From 41976b8d1201b185c2a1ff692a11b830b6fa0589 Mon Sep 17 00:00:00 2001 From: Sangyoon Date: Sat, 8 Nov 2025 04:16:55 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[FEAT]=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=ED=9A=8C=EC=9B=90=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../sampoom/android/MainActivityViewModel.kt | 4 +- .../android/app/navigation/AppNavHost.kt | 20 +- .../core/network/TokenRefreshService.kt | 2 +- .../core/preferences/AuthPreferences.kt | 2 +- .../domain => core/util}/AuthValidator.kt | 2 +- .../feature/auth/data/mapper/AuthMappers.kt | 25 +- .../feature/auth/data/remote/api/AuthApi.kt | 17 +- .../remote/dto/UpdateProfileRequestDto.kt | 8 - .../remote/dto/UpdateProfileResponseDto.kt | 9 - .../data/repository/AuthRepositoryImpl.kt | 35 +- .../auth/domain/repository/AuthRepository.kt | 3 +- .../domain/usecase/GetStoredUserUseCase.kt | 14 - .../auth/domain/usecase/LoginUseCase.kt | 2 +- .../auth/domain/usecase/SignUpUseCase.kt | 2 +- .../android/feature/auth/ui/LoginViewModel.kt | 25 +- .../feature/auth/ui/SignUpViewModel.kt | 36 +- .../feature/dashboard/ui/DashboardScreen.kt | 62 ++-- .../dashboard/ui/DashboardViewModel.kt | 11 +- .../ui/SettingScreen.kt | 49 ++- .../ui/SettingUiEvent.kt | 3 +- .../ui/SettingUiState.kt | 4 +- .../ui/SettingViewModel.kt | 32 +- .../sample/data/local/database/.gitkeep | 0 .../sample/data/local/preferences/.gitkeep | 0 .../feature/sample/data/mapper/.gitkeep | 0 .../feature/sample/data/remote/api/.gitkeep | 0 .../feature/sample/data/remote/dto/.gitkeep | 0 .../feature/sample/data/repository/.gitkeep | 0 .../android/feature/sample/di/.gitkeep | 0 .../feature/sample/domain/model/.gitkeep | 0 .../feature/sample/domain/repository/.gitkeep | 0 .../feature/sample/domain/usecase/.gitkeep | 0 .../android/feature/sample/ui/.gitkeep | 0 .../feature/user/data/mapper/UserMappers.kt | 73 ++++ .../user/data/paging/EmployeePagingSource.kt | 47 +++ .../feature/user/data/remote/api/UserApi.kt | 37 ++ .../data/remote/dto/EditEmployeeRequestDto.kt | 5 + .../remote/dto/EditEmployeeResponseDto.kt | 8 + .../user/data/remote/dto/EmployeeDto.kt | 16 + .../user/data/remote/dto/EmployeeListDto.kt | 15 + .../data/remote/dto/GetProfileResponseDto.kt | 4 +- .../remote/dto/UpdateProfileRequestDto.kt | 5 + .../remote/dto/UpdateProfileResponseDto.kt | 6 + .../data/repository/UserRepositoryImpl.kt | 133 +++++++ .../android/feature/user/di/UserModules.kt | 26 ++ .../feature/user/domain/model/Employee.kt | 16 + .../feature/user/domain/model/EmployeeList.kt | 11 + .../{auth => user}/domain/model/User.kt | 4 +- .../user/domain/repository/UserRepository.kt | 14 + .../domain/usecase/EditEmployeeUseCase.kt | 11 + .../user/domain/usecase/GetEmployeeUseCase.kt | 13 + .../user/domain/usecase/GetProfileUseCase.kt | 11 + .../domain/usecase/GetStoredUserUseCase.kt | 11 + .../domain/usecase/UpdateProfileUseCase.kt | 11 + .../user/ui/EditEmployeeBottomSheet.kt | 113 ++++++ .../feature/user/ui/EditEmployeeUiEvent.kt | 10 + .../feature/user/ui/EditEmployeeUiState.kt | 10 + .../feature/user/ui/EditEmployeeViewModel.kt | 105 ++++++ .../feature/user/ui/EmployeeListScreen.kt | 339 ++++++++++++++++++ .../feature/user/ui/EmployeeListUiEvent.kt | 10 + .../feature/user/ui/EmployeeListUiState.kt | 10 + .../feature/user/ui/EmployeeListViewModel.kt | 47 +++ .../user/ui/UpdateProfileBottomSheet.kt | 84 +++++ .../feature/user/ui/UpdateProfileUiEvent.kt | 9 + .../feature/user/ui/UpdateProfileUiState.kt | 10 + .../feature/user/ui/UpdateProfileViewModel.kt | 100 ++++++ app/src/main/res/values/strings.xml | 12 + gradle/libs.versions.toml | 2 + 69 files changed, 1505 insertions(+), 201 deletions(-) rename app/src/main/java/com/sampoom/android/{feature/auth/domain => core/util}/AuthValidator.kt (98%) delete mode 100644 app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/UpdateProfileRequestDto.kt delete mode 100644 app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/UpdateProfileResponseDto.kt delete mode 100644 app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/GetStoredUserUseCase.kt rename app/src/main/java/com/sampoom/android/feature/{setting => dashboard}/ui/SettingScreen.kt (84%) rename app/src/main/java/com/sampoom/android/feature/{setting => dashboard}/ui/SettingUiEvent.kt (62%) rename app/src/main/java/com/sampoom/android/feature/{setting => dashboard}/ui/SettingUiState.kt (77%) rename app/src/main/java/com/sampoom/android/feature/{setting => dashboard}/ui/SettingViewModel.kt (76%) delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/data/local/database/.gitkeep delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/data/local/preferences/.gitkeep delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/data/mapper/.gitkeep delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/data/remote/api/.gitkeep delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/data/remote/dto/.gitkeep delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/data/repository/.gitkeep delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/di/.gitkeep delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/domain/model/.gitkeep delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/domain/repository/.gitkeep delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/domain/usecase/.gitkeep delete mode 100644 app/src/main/java/com/sampoom/android/feature/sample/ui/.gitkeep create mode 100644 app/src/main/java/com/sampoom/android/feature/user/data/mapper/UserMappers.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/data/paging/EmployeePagingSource.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/data/remote/api/UserApi.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EditEmployeeRequestDto.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EditEmployeeResponseDto.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EmployeeDto.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EmployeeListDto.kt rename app/src/main/java/com/sampoom/android/feature/{auth => user}/data/remote/dto/GetProfileResponseDto.kt (82%) create mode 100644 app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateProfileRequestDto.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateProfileResponseDto.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/di/UserModules.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/domain/model/Employee.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/domain/model/EmployeeList.kt rename app/src/main/java/com/sampoom/android/feature/{auth => user}/domain/model/User.kt (87%) create mode 100644 app/src/main/java/com/sampoom/android/feature/user/domain/repository/UserRepository.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/domain/usecase/EditEmployeeUseCase.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetEmployeeUseCase.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetProfileUseCase.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetStoredUserUseCase.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/domain/usecase/UpdateProfileUseCase.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeBottomSheet.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeUiEvent.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeUiState.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeViewModel.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListUiEvent.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListUiState.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListViewModel.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileBottomSheet.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileUiEvent.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileUiState.kt create mode 100644 app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7d05d41..4bf2b53 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -69,6 +69,7 @@ kotlin { dependencies { // hilt implementation(libs.hilt.android) + implementation(libs.androidx.material3) ksp(libs.hilt.android.compiler) implementation(libs.androidx.hilt.lifecycle.viewmodel.compose) implementation(libs.androidx.hilt.navigation.compose) diff --git a/app/src/main/java/com/sampoom/android/MainActivityViewModel.kt b/app/src/main/java/com/sampoom/android/MainActivityViewModel.kt index 798b771..e1da791 100644 --- a/app/src/main/java/com/sampoom/android/MainActivityViewModel.kt +++ b/app/src/main/java/com/sampoom/android/MainActivityViewModel.kt @@ -3,8 +3,8 @@ package com.sampoom.android import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.sampoom.android.core.util.GlobalMessageHandler -import com.sampoom.android.feature.auth.domain.model.User -import com.sampoom.android.feature.auth.domain.usecase.GetStoredUserUseCase +import com.sampoom.android.feature.user.domain.model.User +import com.sampoom.android.feature.user.domain.usecase.GetStoredUserUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt b/app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt index e8f66bc..44eb2fb 100644 --- a/app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt +++ b/app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt @@ -1,9 +1,7 @@ package com.sampoom.android.app.navigation import android.annotation.SuppressLint -import androidx.activity.ComponentActivity import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.CircularProgressIndicator @@ -25,11 +23,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.core.view.WindowInsetsControllerCompat import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController @@ -49,18 +44,19 @@ import com.sampoom.android.core.ui.theme.Main500 import com.sampoom.android.core.ui.theme.backgroundCardColor import com.sampoom.android.core.ui.theme.backgroundColor import com.sampoom.android.core.ui.theme.textColor -import com.sampoom.android.feature.auth.domain.model.User import com.sampoom.android.feature.auth.ui.AuthViewModel import com.sampoom.android.feature.auth.ui.LoginScreen import com.sampoom.android.feature.auth.ui.SignUpScreen import com.sampoom.android.feature.cart.ui.CartListScreen import com.sampoom.android.feature.dashboard.ui.DashboardScreen +import com.sampoom.android.feature.dashboard.ui.SettingScreen import com.sampoom.android.feature.order.ui.OrderDetailScreen import com.sampoom.android.feature.order.ui.OrderListScreen import com.sampoom.android.feature.outbound.ui.OutboundListScreen import com.sampoom.android.feature.part.ui.PartListScreen import com.sampoom.android.feature.part.ui.PartScreen -import com.sampoom.android.feature.setting.ui.SettingScreen +import com.sampoom.android.feature.user.domain.model.User +import com.sampoom.android.feature.user.ui.EmployeeListScreen import kotlinx.coroutines.flow.filterNotNull // Auth Screen @@ -216,6 +212,13 @@ fun AppNavHost( } ) } + composable(ROUTE_EMPLOYEE) { + EmployeeListScreen( + onNavigateBack = { + navController.navigateUp() + } + ) + } } TopSnackBarHost(hostState = snackBarHostState, isError = currentMessage?.isError ?: false) } @@ -238,6 +241,9 @@ fun MainScreen( composable(ROUTE_DASHBOARD) { DashboardScreen( paddingValues = innerPadding, + onEmployeeClick = { + parentNavController.navigate(ROUTE_EMPLOYEE) + }, onSettingClick = { parentNavController.navigate(ROUTE_SETTINGS) }, diff --git a/app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt b/app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt index d2db843..1cf693d 100644 --- a/app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt +++ b/app/src/main/java/com/sampoom/android/core/network/TokenRefreshService.kt @@ -3,7 +3,7 @@ package com.sampoom.android.core.network import com.sampoom.android.core.preferences.AuthPreferences import com.sampoom.android.feature.auth.data.remote.api.AuthApi import com.sampoom.android.feature.auth.data.remote.dto.RefreshRequestDto -import com.sampoom.android.feature.auth.domain.model.User +import com.sampoom.android.feature.user.domain.model.User import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import javax.inject.Inject diff --git a/app/src/main/java/com/sampoom/android/core/preferences/AuthPreferences.kt b/app/src/main/java/com/sampoom/android/core/preferences/AuthPreferences.kt index 5b3faeb..d4c0473 100644 --- a/app/src/main/java/com/sampoom/android/core/preferences/AuthPreferences.kt +++ b/app/src/main/java/com/sampoom/android/core/preferences/AuthPreferences.kt @@ -7,7 +7,7 @@ import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.sampoom.android.core.model.UserPosition -import com.sampoom.android.feature.auth.domain.model.User +import com.sampoom.android.feature.user.domain.model.User import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/com/sampoom/android/feature/auth/domain/AuthValidator.kt b/app/src/main/java/com/sampoom/android/core/util/AuthValidator.kt similarity index 98% rename from app/src/main/java/com/sampoom/android/feature/auth/domain/AuthValidator.kt rename to app/src/main/java/com/sampoom/android/core/util/AuthValidator.kt index b7c4516..e6019f6 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/domain/AuthValidator.kt +++ b/app/src/main/java/com/sampoom/android/core/util/AuthValidator.kt @@ -1,4 +1,4 @@ -package com.sampoom.android.feature.auth.domain +package com.sampoom.android.core.util import com.sampoom.android.R diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt index 206707c..6b130cc 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt @@ -1,11 +1,10 @@ package com.sampoom.android.feature.auth.data.mapper import com.sampoom.android.core.model.UserPosition -import com.sampoom.android.feature.auth.data.remote.dto.GetProfileResponseDto import com.sampoom.android.feature.auth.data.remote.dto.GetVendorsResponseDto import com.sampoom.android.feature.auth.data.remote.dto.LoginResponseDto -import com.sampoom.android.feature.auth.domain.model.User import com.sampoom.android.feature.auth.domain.model.Vendor +import com.sampoom.android.feature.user.domain.model.User fun LoginResponseDto.toModel(): User = User( userId = userId, @@ -23,28 +22,6 @@ fun LoginResponseDto.toModel(): User = User( endedAt = null ) -fun GetProfileResponseDto.toModel(): User = User( - userId = userId, - userName = userName, - email = email, - role = role, - accessToken = "", - refreshToken = "", - expiresIn = 0L, - position = position.toUserPosition(), - workspace = workspace, - branch = branch, - agencyId = organizationId, - startedAt = startedAt, - endedAt = endedAt -) - -private fun String.toUserPosition(): UserPosition = try { - UserPosition.valueOf(this.uppercase()) -} catch (_: IllegalArgumentException) { - UserPosition.STAFF -} - fun GetVendorsResponseDto.toModel(): Vendor = Vendor( id = id, vendorCode = vendorCode, diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt index fbbf1fb..d20d25d 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt @@ -2,22 +2,17 @@ package com.sampoom.android.feature.auth.data.remote.api import com.sampoom.android.core.model.ApiResponse import com.sampoom.android.core.model.ApiSuccessResponse +import com.sampoom.android.feature.auth.data.remote.dto.GetVendorsResponseDto import com.sampoom.android.feature.auth.data.remote.dto.LoginRequestDto -import com.sampoom.android.feature.auth.data.remote.dto.SignUpRequestDto -import com.sampoom.android.feature.auth.data.remote.dto.SignUpResponseDto import com.sampoom.android.feature.auth.data.remote.dto.LoginResponseDto import com.sampoom.android.feature.auth.data.remote.dto.RefreshRequestDto import com.sampoom.android.feature.auth.data.remote.dto.RefreshResponseDto -import com.sampoom.android.feature.auth.data.remote.dto.UpdateProfileRequestDto -import com.sampoom.android.feature.auth.data.remote.dto.UpdateProfileResponseDto -import com.sampoom.android.feature.auth.data.remote.dto.GetProfileResponseDto -import com.sampoom.android.feature.auth.data.remote.dto.GetVendorsResponseDto +import com.sampoom.android.feature.auth.data.remote.dto.SignUpRequestDto +import com.sampoom.android.feature.auth.data.remote.dto.SignUpResponseDto import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Headers -import retrofit2.http.PATCH import retrofit2.http.POST -import retrofit2.http.Query interface AuthApi { @POST("auth/signup") @@ -34,12 +29,6 @@ interface AuthApi { @Headers("X-No-Auth: true") suspend fun login(@Body body: LoginRequestDto): ApiResponse - @GET("user/profile") - suspend fun getProfile(@Query("workspace") workspace: String): ApiResponse - - @PATCH("user/profile") - suspend fun updateProfile(@Body body: UpdateProfileRequestDto): ApiResponse - @GET("site/vendors") suspend fun getVendors(): ApiResponse> } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/UpdateProfileRequestDto.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/UpdateProfileRequestDto.kt deleted file mode 100644 index 8db66be..0000000 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/UpdateProfileRequestDto.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.sampoom.android.feature.auth.data.remote.dto - -data class UpdateProfileRequestDto( - val userName: String, - val position: String, - val workspace: String, - val branch: String -) diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/UpdateProfileResponseDto.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/UpdateProfileResponseDto.kt deleted file mode 100644 index 12e2ab6..0000000 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/UpdateProfileResponseDto.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.sampoom.android.feature.auth.data.remote.dto - -data class UpdateProfileResponseDto( - val userId: Long, - val userName: String, - val position: String, - val workspace: String, - val branch: String -) diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt index cdc8642..f1e5106 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt @@ -7,10 +7,9 @@ import com.sampoom.android.feature.auth.data.remote.api.AuthApi import com.sampoom.android.feature.auth.data.remote.dto.LoginRequestDto import com.sampoom.android.feature.auth.data.remote.dto.RefreshRequestDto import com.sampoom.android.feature.auth.data.remote.dto.SignUpRequestDto -import com.sampoom.android.feature.auth.domain.model.User import com.sampoom.android.feature.auth.domain.model.VendorList import com.sampoom.android.feature.auth.domain.repository.AuthRepository -import com.sampoom.android.feature.outbound.data.mapper.toModel +import com.sampoom.android.feature.user.domain.model.User import kotlinx.coroutines.delay import javax.inject.Inject @@ -63,29 +62,7 @@ class AuthRepositoryImpl @Inject constructor( val loginUser = loginDto.data.toModel() preferences.saveUser(loginUser) - - val profileUser = retry(times = 5, initialDelay = 300) { - getProfile("AGENCY").getOrThrow() - } - - val user = User( - userId = loginUser.userId, - userName = profileUser.userName, - email = profileUser.email, - role = profileUser.role, - accessToken = loginUser.accessToken, - refreshToken = loginUser.refreshToken, - expiresIn = loginUser.expiresIn, - position = profileUser.position, - workspace = profileUser.workspace, - branch = profileUser.branch, - agencyId = profileUser.agencyId, - startedAt = profileUser.startedAt, - endedAt = profileUser.endedAt - ) - - preferences.saveUser(user) - user + loginUser } } @@ -126,14 +103,6 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun isSignedIn(): Boolean = preferences.hasToken() - override suspend fun getProfile(workspace: String): Result { - return runCatching { - val dto = api.getProfile(workspace) - if (!dto.success) throw Exception(dto.message) - dto.data.toModel() - } - } - override suspend fun getVendorList(): Result { return runCatching { val dto = api.getVendors() diff --git a/app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt b/app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt index 7d9e833..10e95e1 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt @@ -1,7 +1,7 @@ package com.sampoom.android.feature.auth.domain.repository -import com.sampoom.android.feature.auth.domain.model.User import com.sampoom.android.feature.auth.domain.model.VendorList +import com.sampoom.android.feature.user.domain.model.User interface AuthRepository { suspend fun signUp( @@ -18,6 +18,5 @@ interface AuthRepository { suspend fun refreshToken(): Result suspend fun clearTokens(): Result suspend fun isSignedIn(): Boolean - suspend fun getProfile(workspace: String): Result suspend fun getVendorList(): Result } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/GetStoredUserUseCase.kt b/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/GetStoredUserUseCase.kt deleted file mode 100644 index 9401624..0000000 --- a/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/GetStoredUserUseCase.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.sampoom.android.feature.auth.domain.usecase - -import com.sampoom.android.core.preferences.AuthPreferences -import com.sampoom.android.feature.auth.domain.model.User -import javax.inject.Inject - -class GetStoredUserUseCase @Inject constructor( - private val preferences: AuthPreferences -) { - suspend operator fun invoke(): User? = preferences.getStoredUser() -} - - - diff --git a/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/LoginUseCase.kt b/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/LoginUseCase.kt index 4c47220..af9b253 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/LoginUseCase.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/LoginUseCase.kt @@ -1,6 +1,6 @@ package com.sampoom.android.feature.auth.domain.usecase -import com.sampoom.android.feature.auth.domain.model.User +import com.sampoom.android.feature.user.domain.model.User import com.sampoom.android.feature.auth.domain.repository.AuthRepository import javax.inject.Inject diff --git a/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/SignUpUseCase.kt b/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/SignUpUseCase.kt index 9e5ec23..5dc6d2b 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/SignUpUseCase.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/SignUpUseCase.kt @@ -1,6 +1,6 @@ package com.sampoom.android.feature.auth.domain.usecase -import com.sampoom.android.feature.auth.domain.model.User +import com.sampoom.android.feature.user.domain.model.User import com.sampoom.android.feature.auth.domain.repository.AuthRepository import javax.inject.Inject diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt index 9e06634..baa24dc 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt @@ -6,9 +6,10 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.sampoom.android.core.network.serverMessageOrNull import com.sampoom.android.core.util.GlobalMessageHandler -import com.sampoom.android.feature.auth.domain.AuthValidator -import com.sampoom.android.feature.auth.domain.ValidationResult +import com.sampoom.android.core.util.AuthValidator +import com.sampoom.android.core.util.ValidationResult import com.sampoom.android.feature.auth.domain.usecase.LoginUseCase +import com.sampoom.android.feature.user.domain.usecase.GetProfileUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -20,6 +21,7 @@ import javax.inject.Inject class LoginViewModel @Inject constructor( private val messageHandler: GlobalMessageHandler, private val singIn: LoginUseCase, + private val getProfile: GetProfileUseCase, private val application: Application ) : ViewModel() { @@ -86,9 +88,21 @@ class LoginViewModel @Inject constructor( _uiState.update { it.copy(loading = true, success = false) } singIn(s.email, s.password) .onSuccess { - _uiState.update { - it.copy(loading = false, success = true) - } + getProfile("AGENCY") + .onSuccess { + _uiState.update { + it.copy(loading = false, success = true) + } + } + .onFailure { throwable -> + val backendMessage = throwable.serverMessageOrNull() + val error = backendMessage ?: (throwable.message ?: errorLabel) + messageHandler.showMessage(message = error, isError = true) + + _uiState.update { + it.copy(loading = false, success = false) + } + } } .onFailure { throwable -> val backendMessage = throwable.serverMessageOrNull() @@ -98,6 +112,7 @@ class LoginViewModel @Inject constructor( _uiState.update { it.copy(loading = false, success = false) } + return@launch } Log.d(TAG, "submit: ${_uiState.value}") } diff --git a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt index a448e7b..3c43a25 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt @@ -7,13 +7,14 @@ import androidx.lifecycle.viewModelScope import com.sampoom.android.R import com.sampoom.android.core.network.serverMessageOrNull import com.sampoom.android.core.util.GlobalMessageHandler -import com.sampoom.android.feature.auth.domain.AuthValidator -import com.sampoom.android.feature.auth.domain.AuthValidator.validateEmail -import com.sampoom.android.feature.auth.domain.AuthValidator.validatePassword -import com.sampoom.android.feature.auth.domain.AuthValidator.validatePasswordCheck -import com.sampoom.android.feature.auth.domain.ValidationResult +import com.sampoom.android.core.util.AuthValidator +import com.sampoom.android.core.util.AuthValidator.validateEmail +import com.sampoom.android.core.util.AuthValidator.validatePassword +import com.sampoom.android.core.util.AuthValidator.validatePasswordCheck +import com.sampoom.android.core.util.ValidationResult import com.sampoom.android.feature.auth.domain.usecase.GetVendorUseCase import com.sampoom.android.feature.auth.domain.usecase.SignUpUseCase +import com.sampoom.android.feature.user.domain.usecase.GetProfileUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -26,6 +27,7 @@ class SignUpViewModel @Inject constructor( private val messageHandler: GlobalMessageHandler, private val singUp: SignUpUseCase, private val getVendorUseCase: GetVendorUseCase, + private val getProfileUseCase: GetProfileUseCase, private val application: Application ) : ViewModel() { @@ -118,21 +120,21 @@ class SignUpViewModel @Inject constructor( } private fun validateEmail() { - val result = AuthValidator.validateEmail(_state.value.email) + val result = validateEmail(_state.value.email) _state.value = _state.value.copy( emailError = result.toErrorMessage() ) } private fun validatePassword() { - val result = AuthValidator.validatePassword(_state.value.password) + val result = validatePassword(_state.value.password) _state.value = _state.value.copy( passwordError = result.toErrorMessage() ) } private fun validatePasswordCheck() { - val result = AuthValidator.validatePasswordCheck(_state.value.password, _state.value.passwordCheck) + val result = validatePasswordCheck(_state.value.password, _state.value.passwordCheck) _state.value = _state.value.copy( passwordCheckError = result.toErrorMessage() ) @@ -167,9 +169,21 @@ class SignUpViewModel @Inject constructor( position = s.position!!.name ) .onSuccess { - _state.update { - it.copy(loading = false, success = true) - } + getProfileUseCase("AGENCY") + .onSuccess { + _state.update { + it.copy(loading = false, success = true) + } + } + .onFailure { throwable -> + val backendMessage = throwable.serverMessageOrNull() + val error = backendMessage ?: (throwable.message ?: errorLabel) + messageHandler.showMessage(message = error, isError = true) + + _state.update { + it.copy(loading = false, success = false) + } + } } .onFailure { throwable -> val backendMessage = throwable.serverMessageOrNull() diff --git a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt index 4971cc0..8fc4a11 100644 --- a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt @@ -47,7 +47,6 @@ import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import com.sampoom.android.R -import com.sampoom.android.core.model.UserPosition import com.sampoom.android.core.ui.component.EmptyContent import com.sampoom.android.core.ui.component.ErrorContent import com.sampoom.android.core.ui.theme.FailRed @@ -56,15 +55,16 @@ import com.sampoom.android.core.ui.theme.SuccessGreen import com.sampoom.android.core.ui.theme.backgroundCardColor import com.sampoom.android.core.ui.theme.textColor import com.sampoom.android.core.ui.theme.textSecondaryColor -import com.sampoom.android.feature.auth.domain.model.User import com.sampoom.android.feature.dashboard.domain.model.Dashboard import com.sampoom.android.feature.dashboard.domain.model.WeeklySummary import com.sampoom.android.feature.order.domain.model.Order import com.sampoom.android.feature.order.ui.OrderItem +import com.sampoom.android.feature.user.domain.model.User @Composable fun DashboardScreen( paddingValues: PaddingValues, + onEmployeeClick: () -> Unit, onSettingClick: () -> Unit, onNavigateOrderDetail: (Order) -> Unit, onNavigationOrder: () -> Unit, @@ -75,20 +75,7 @@ fun DashboardScreen( val user by viewModel.user.collectAsStateWithLifecycle() val pullRefreshState = rememberPullToRefreshState() val orderListPaged = viewModel.orderListPaged.collectAsLazyPagingItems() - val isManager = when (user?.position) { - UserPosition.STAFF, - UserPosition.SENIOR_STAFF, - UserPosition.ASSISTANT_MANAGER, - UserPosition.MANAGER, - UserPosition.DEPUTY_GENERAL_MANAGER, - UserPosition.GENERAL_MANAGER, - UserPosition.DIRECTOR, - UserPosition.VICE_PRESIDENT, - UserPosition.PRESIDENT, - UserPosition.CHAIRMAN -> true - - else -> false - } + val isManager = user?.role == "ADMIN" LaunchedEffect(errorLabel) { viewModel.bindLabel(errorLabel) @@ -99,6 +86,7 @@ fun DashboardScreen( onRefresh = { viewModel.onEvent(DashboardUiEvent.LoadDashboard) orderListPaged.refresh() + viewModel.refreshUser() }, state = pullRefreshState, modifier = Modifier.fillMaxSize(), @@ -135,7 +123,7 @@ fun DashboardScreen( Row { if (isManager) { IconButton( - onClick = { } + onClick = { onEmployeeClick() } ) { Icon( painter = painterResource(R.drawable.employee), @@ -163,7 +151,13 @@ fun DashboardScreen( ) { item { TitleSection(user) } - item { ButtonSection(isManager, uiState.dashboard) } + item { + ButtonSection( + isManager = isManager, + dashboard = uiState.dashboard, + onEmployeeClick = { onEmployeeClick() } + ) + } item { OrderListSection( @@ -234,6 +228,7 @@ fun TitleSection( @Composable fun ButtonSection( + onEmployeeClick: () -> Unit, isManager: Boolean, dashboard: Dashboard? ) { @@ -254,7 +249,7 @@ fun ButtonSection( painterDescription = stringResource(R.string.dashboard_employee), text = 45.toString(), // TODO : API 연동 subText = stringResource(R.string.dashboard_employee), - onClick = { } + onClick = { onEmployeeClick() } ) } @@ -267,17 +262,16 @@ fun ButtonSection( painter = painterResource(R.drawable.car), painterDescription = stringResource(R.string.dashboard_parts_all), text = (dashboard?.totalParts ?: stringResource(R.string.common_slash)).toString(), - subText = stringResource(R.string.dashboard_parts_all), - onClick = { } + subText = stringResource(R.string.dashboard_parts_all) ) ButtonCard( modifier = Modifier.weight(1f), painter = painterResource(R.drawable.block), painterDescription = stringResource(R.string.dashboard_parts_out_of_stock), - text = (dashboard?.outOfStockParts ?: stringResource(R.string.common_slash)).toString(), - subText = stringResource(R.string.dashboard_parts_out_of_stock), - onClick = { } + text = (dashboard?.outOfStockParts + ?: stringResource(R.string.common_slash)).toString(), + subText = stringResource(R.string.dashboard_parts_out_of_stock) ) } @@ -289,18 +283,18 @@ fun ButtonSection( modifier = Modifier.weight(1f), painter = painterResource(R.drawable.warning), painterDescription = stringResource(R.string.dashboard_parts_low_stock), - text = (dashboard?.lowStockParts ?: stringResource(R.string.common_slash)).toString(), - subText = stringResource(R.string.dashboard_parts_low_stock), - onClick = { } + text = (dashboard?.lowStockParts + ?: stringResource(R.string.common_slash)).toString(), + subText = stringResource(R.string.dashboard_parts_low_stock) ) ButtonCard( modifier = Modifier.weight(1f), painter = painterResource(R.drawable.parts), painterDescription = stringResource(R.string.dashboard_parts_on_hand), - text = (dashboard?.totalQuantity ?: stringResource(R.string.common_slash)).toString(), - subText = stringResource(R.string.dashboard_parts_on_hand), - onClick = { } + text = (dashboard?.totalQuantity + ?: stringResource(R.string.common_slash)).toString(), + subText = stringResource(R.string.dashboard_parts_on_hand) ) } } @@ -313,7 +307,7 @@ fun ButtonCard( painterDescription: String, text: String, subText: String, - onClick: () -> Unit + onClick: () -> Unit = {} ) { Card( modifier = modifier @@ -484,7 +478,8 @@ fun WeeklySummarySection( horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = (weeklySummary?.inStockParts ?: stringResource(R.string.common_slash)).toString(), + text = (weeklySummary?.inStockParts + ?: stringResource(R.string.common_slash)).toString(), style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, color = SuccessGreen @@ -501,7 +496,8 @@ fun WeeklySummarySection( horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = (weeklySummary?.outStockParts ?: stringResource(R.string.common_slash)).toString(), + text = (weeklySummary?.outStockParts + ?: stringResource(R.string.common_slash)).toString(), style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, color = FailRed diff --git a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardViewModel.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardViewModel.kt index f0e6e4e..afc5613 100644 --- a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardViewModel.kt @@ -6,14 +6,13 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import com.sampoom.android.core.network.serverMessageOrNull import com.sampoom.android.core.util.GlobalMessageHandler -import com.sampoom.android.feature.auth.domain.model.User -import com.sampoom.android.feature.auth.domain.usecase.GetStoredUserUseCase import com.sampoom.android.feature.dashboard.domain.usecase.GetDashboardUseCase import com.sampoom.android.feature.dashboard.domain.usecase.WeeklySummaryUseCase import com.sampoom.android.feature.order.domain.model.Order import com.sampoom.android.feature.order.domain.usecase.GetOrderUseCase +import com.sampoom.android.feature.user.domain.model.User +import com.sampoom.android.feature.user.domain.usecase.GetStoredUserUseCase import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -127,4 +126,10 @@ class DashboardViewModel @Inject constructor( } } } + + fun refreshUser() { + viewModelScope.launch { + _user.value = getStoredUserUseCase() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/setting/ui/SettingScreen.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingScreen.kt similarity index 84% rename from app/src/main/java/com/sampoom/android/feature/setting/ui/SettingScreen.kt rename to app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingScreen.kt index 46b47e0..90f51a0 100644 --- a/app/src/main/java/com/sampoom/android/feature/setting/ui/SettingScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingScreen.kt @@ -1,4 +1,4 @@ -package com.sampoom.android.feature.setting.ui +package com.sampoom.android.feature.dashboard.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -16,6 +16,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -23,11 +24,13 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -36,14 +39,19 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import com.sampoom.android.R +import com.sampoom.android.core.ui.theme.FailRed import com.sampoom.android.core.ui.theme.backgroundCardColor import com.sampoom.android.core.ui.theme.disableColor import com.sampoom.android.core.ui.theme.textColor import com.sampoom.android.core.ui.theme.textSecondaryColor import com.sampoom.android.core.util.formatDate import com.sampoom.android.core.util.positionToKorean -import com.sampoom.android.feature.auth.domain.model.User +import com.sampoom.android.feature.user.domain.model.User +import com.sampoom.android.feature.user.ui.UpdateProfileBottomSheet +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -52,12 +60,15 @@ fun SettingScreen( onNavigateBack: () -> Unit = {}, onLogoutClick: () -> Unit = {} ) { + val coroutineScope = rememberCoroutineScope() val errorLabel = stringResource(R.string.common_error) val nameLabel = stringResource(R.string.signup_title_name) val uiState by viewModel.uiState.collectAsStateWithLifecycle() val user by viewModel.user.collectAsStateWithLifecycle() val pullRefreshState = rememberPullToRefreshState() var showLogoutDialog by remember { mutableStateOf(false) } + var showEditProfileSheet by remember { mutableStateOf(false) } + val sheetState = rememberModalBottomSheetState(true) LaunchedEffect(errorLabel, nameLabel) { viewModel.bindLabel(errorLabel, nameLabel) @@ -72,7 +83,10 @@ fun SettingScreen( PullToRefreshBox( isRefreshing = false, - onRefresh = { viewModel.onEvent(SettingUiEvent.LoadProfile) }, + onRefresh = { + viewModel.onEvent(SettingUiEvent.LoadProfile) + viewModel.refreshUser() + }, state = pullRefreshState, modifier = Modifier.fillMaxSize(), indicator = { @@ -114,7 +128,7 @@ fun SettingScreen( } item { SettingSection( - onEditProfileClick = { }, + onEditProfileClick = { showEditProfileSheet = true }, onLogoutClick = { showLogoutDialog = true } ) } @@ -122,6 +136,31 @@ fun SettingScreen( } } + if (showEditProfileSheet && user != null) { + ModalBottomSheet( + onDismissRequest = { + coroutineScope.launch { + showEditProfileSheet = false + sheetState.hide() + } + }, + sheetState = sheetState + ) { + UpdateProfileBottomSheet( + user = user!!, + onProfileUpdated = { + viewModel.refreshUser() + }, + onDismiss = { + coroutineScope.launch { + showEditProfileSheet = false + sheetState.hide() + } + } + ) + } + } + if (showLogoutDialog) { AlertDialog( onDismissRequest = { showLogoutDialog = false }, @@ -247,7 +286,7 @@ private fun SettingSection( Text( text = stringResource(R.string.setting_logout), style = MaterialTheme.typography.titleMedium, - color = textColor() + color = FailRed ) Icon( diff --git a/app/src/main/java/com/sampoom/android/feature/setting/ui/SettingUiEvent.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingUiEvent.kt similarity index 62% rename from app/src/main/java/com/sampoom/android/feature/setting/ui/SettingUiEvent.kt rename to app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingUiEvent.kt index 4845ecd..6cae162 100644 --- a/app/src/main/java/com/sampoom/android/feature/setting/ui/SettingUiEvent.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingUiEvent.kt @@ -1,7 +1,6 @@ -package com.sampoom.android.feature.setting.ui +package com.sampoom.android.feature.dashboard.ui sealed interface SettingUiEvent { object LoadProfile : SettingUiEvent data class NameChanged(val userName: String) : SettingUiEvent - object EditProfile : SettingUiEvent } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/setting/ui/SettingUiState.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingUiState.kt similarity index 77% rename from app/src/main/java/com/sampoom/android/feature/setting/ui/SettingUiState.kt rename to app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingUiState.kt index caad258..9076f30 100644 --- a/app/src/main/java/com/sampoom/android/feature/setting/ui/SettingUiState.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingUiState.kt @@ -1,6 +1,6 @@ -package com.sampoom.android.feature.setting.ui +package com.sampoom.android.feature.dashboard.ui -import com.sampoom.android.feature.auth.domain.model.User +import com.sampoom.android.feature.user.domain.model.User data class SettingUiState( val profile: User? = null, diff --git a/app/src/main/java/com/sampoom/android/feature/setting/ui/SettingViewModel.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingViewModel.kt similarity index 76% rename from app/src/main/java/com/sampoom/android/feature/setting/ui/SettingViewModel.kt rename to app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingViewModel.kt index 2cab7bc..4d61c17 100644 --- a/app/src/main/java/com/sampoom/android/feature/setting/ui/SettingViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/SettingViewModel.kt @@ -1,13 +1,13 @@ -package com.sampoom.android.feature.setting.ui +package com.sampoom.android.feature.dashboard.ui import android.app.Application import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.sampoom.android.core.util.GlobalMessageHandler -import com.sampoom.android.feature.auth.domain.AuthValidator -import com.sampoom.android.feature.auth.domain.ValidationResult -import com.sampoom.android.feature.auth.domain.model.User -import com.sampoom.android.feature.auth.domain.usecase.GetStoredUserUseCase +import com.sampoom.android.core.util.AuthValidator +import com.sampoom.android.core.util.ValidationResult +import com.sampoom.android.feature.user.domain.model.User +import com.sampoom.android.feature.user.domain.usecase.GetStoredUserUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -48,12 +48,13 @@ class SettingViewModel @Inject constructor( fun onEvent(event: SettingUiEvent) { when (event) { - is SettingUiEvent.LoadProfile -> {} + is SettingUiEvent.LoadProfile -> { + refreshUser() + } is SettingUiEvent.NameChanged -> { _uiState.value = _uiState.value.copy(userName = event.userName) validateName() } - is SettingUiEvent.EditProfile -> editProfile() } } @@ -72,18 +73,13 @@ class SettingViewModel @Inject constructor( } } - private fun editProfile() = viewModelScope.launch { - validateName() - - if (!_uiState.value.isValid) return@launch - - val s = _uiState.value - _uiState.update { it.copy(loading = true) } - - // TODO : Edit Profile 연동 - } - fun clearSuccess() { _uiState.update { it.copy(profileChangeSuccess = false, logoutSuccess = false) } } + + fun refreshUser() { + viewModelScope.launch { + _user.value = getStoredUserUseCase() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/sample/data/local/database/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/data/local/database/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/sample/data/local/preferences/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/data/local/preferences/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/sample/data/mapper/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/data/mapper/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/sample/data/remote/api/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/data/remote/api/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/sample/data/remote/dto/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/data/remote/dto/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/sample/data/repository/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/data/repository/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/sample/di/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/di/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/sample/domain/model/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/domain/model/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/sample/domain/repository/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/domain/repository/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/sample/domain/usecase/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/domain/usecase/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/sample/ui/.gitkeep b/app/src/main/java/com/sampoom/android/feature/sample/ui/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/mapper/UserMappers.kt b/app/src/main/java/com/sampoom/android/feature/user/data/mapper/UserMappers.kt new file mode 100644 index 0000000..62ec635 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/data/mapper/UserMappers.kt @@ -0,0 +1,73 @@ +package com.sampoom.android.feature.user.data.mapper + +import com.sampoom.android.core.model.UserPosition +import com.sampoom.android.feature.user.data.remote.dto.EditEmployeeResponseDto +import com.sampoom.android.feature.user.data.remote.dto.EmployeeDto +import com.sampoom.android.feature.user.data.remote.dto.GetProfileResponseDto +import com.sampoom.android.feature.user.data.remote.dto.UpdateProfileResponseDto +import com.sampoom.android.feature.user.domain.model.Employee +import com.sampoom.android.feature.user.domain.model.User + +fun GetProfileResponseDto.toModel(): User = User( + userId = userId, + userName = userName, + email = email, + role = role, + accessToken = "", + refreshToken = "", + expiresIn = 0L, + position = position.toUserPosition(), + workspace = workspace, + branch = branch, + agencyId = organizationId, + startedAt = startedAt, + endedAt = endedAt +) + +private fun String.toUserPosition(): UserPosition = try { + UserPosition.valueOf(this.uppercase()) +} catch (_: IllegalArgumentException) { + UserPosition.STAFF +} + +fun UpdateProfileResponseDto.toModel(): User = User( + userId = userId, + userName = userName, + email = "", + role = "", + accessToken = "", + refreshToken = "", + expiresIn = 0L, + position = UserPosition.STAFF, + workspace = "", + branch = "", + agencyId = 0, + startedAt = null, + endedAt = null +) + +fun EditEmployeeResponseDto.toModel(): Employee = Employee( + userId = userId, + email = "", + role = "", + userName = userName, + workspace = workspace, + organizationId = 0, + branch = "", + position = position.toUserPosition(), + startedAt = null, + endedAt = null +) + +fun EmployeeDto.toModel(): Employee = Employee( + userId, + email, + role, + userName, + workspace, + organizationId, + branch, + position, + startedAt, + endedAt +) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/paging/EmployeePagingSource.kt b/app/src/main/java/com/sampoom/android/feature/user/data/paging/EmployeePagingSource.kt new file mode 100644 index 0000000..621f4ba --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/data/paging/EmployeePagingSource.kt @@ -0,0 +1,47 @@ +package com.sampoom.android.feature.user.data.paging + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.sampoom.android.core.preferences.AuthPreferences +import com.sampoom.android.feature.user.data.mapper.toModel +import com.sampoom.android.feature.user.data.remote.api.UserApi +import com.sampoom.android.feature.user.domain.model.Employee +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +class EmployeePagingSource @AssistedInject constructor( + private val api: UserApi, + private val authPreferences: AuthPreferences +) : PagingSource() { + + @AssistedFactory + interface Factory { + fun create(): EmployeePagingSource + } + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) + } + } + + override suspend fun load(params: LoadParams): LoadResult { + return try { + val agencyId = authPreferences.getStoredUser()?.agencyId ?: throw Exception() + val page = params.key ?: 0 + val pageSize = params.loadSize + val response = api.getEmployeeList("AGENCY", agencyId, page, pageSize) + val employee = response.data.users.map { it.toModel() } + val meta = response.data.meta + + LoadResult.Page( + data = employee, + prevKey = if (meta.hasPrevious) page - 1 else null, + nextKey = if (meta.hasNext) page + 1 else null + ) + } catch (e: Exception) { + LoadResult.Error(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/remote/api/UserApi.kt b/app/src/main/java/com/sampoom/android/feature/user/data/remote/api/UserApi.kt new file mode 100644 index 0000000..e54ee76 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/data/remote/api/UserApi.kt @@ -0,0 +1,37 @@ +package com.sampoom.android.feature.user.data.remote.api + +import com.sampoom.android.core.model.ApiResponse +import com.sampoom.android.feature.user.data.remote.dto.GetProfileResponseDto +import com.sampoom.android.feature.user.data.remote.dto.EditEmployeeRequestDto +import com.sampoom.android.feature.user.data.remote.dto.EditEmployeeResponseDto +import com.sampoom.android.feature.user.data.remote.dto.EmployeeListDto +import com.sampoom.android.feature.user.data.remote.dto.UpdateProfileRequestDto +import com.sampoom.android.feature.user.data.remote.dto.UpdateProfileResponseDto +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.PATCH +import retrofit2.http.Path +import retrofit2.http.Query + +interface UserApi { + @GET("user/profile") + suspend fun getProfile(@Query("workspace") workspace: String): ApiResponse + + @PATCH("user/profile") + suspend fun updateProfile(@Body body: UpdateProfileRequestDto): ApiResponse + + @PATCH("user/profile/{userId}") + suspend fun editEmployee( + @Path("userId") userId: Long, + @Query("workspace") workspace: String, + @Body body: EditEmployeeRequestDto + ): ApiResponse + + @GET("user/info") + suspend fun getEmployeeList( + @Query("workspace") workspace: String, + @Query("organizationId") organizationId: Long, + @Query("page") page: Int = 0, + @Query("size") size: Int = 20 + ): ApiResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EditEmployeeRequestDto.kt b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EditEmployeeRequestDto.kt new file mode 100644 index 0000000..07b2a36 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EditEmployeeRequestDto.kt @@ -0,0 +1,5 @@ +package com.sampoom.android.feature.user.data.remote.dto + +data class EditEmployeeRequestDto( + val position: String +) diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EditEmployeeResponseDto.kt b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EditEmployeeResponseDto.kt new file mode 100644 index 0000000..700cdce --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EditEmployeeResponseDto.kt @@ -0,0 +1,8 @@ +package com.sampoom.android.feature.user.data.remote.dto + +data class EditEmployeeResponseDto( + val userId: Long, + val userName: String, + val workspace: String, + val position: String +) diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EmployeeDto.kt b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EmployeeDto.kt new file mode 100644 index 0000000..31d78fd --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EmployeeDto.kt @@ -0,0 +1,16 @@ +package com.sampoom.android.feature.user.data.remote.dto + +import com.sampoom.android.core.model.UserPosition + +data class EmployeeDto( + val userId: Long, + val email: String, + val role: String, + val userName: String, + val workspace: String, + val organizationId: Long, + val branch: String, + val position: UserPosition, + val startedAt: String?, + val endedAt: String? +) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EmployeeListDto.kt b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EmployeeListDto.kt new file mode 100644 index 0000000..8ca47a8 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/EmployeeListDto.kt @@ -0,0 +1,15 @@ +package com.sampoom.android.feature.user.data.remote.dto + +data class EmployeeListDto( + val users: List, + val meta: EmployeeMetaDto +) + +data class EmployeeMetaDto( + val currentPage: Int, + val totalPages: Int, + val totalElements: Int, + val size: Int, + val hasNext: Boolean, + val hasPrevious: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/GetProfileResponseDto.kt b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/GetProfileResponseDto.kt similarity index 82% rename from app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/GetProfileResponseDto.kt rename to app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/GetProfileResponseDto.kt index aa46f7d..b54aaef 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/GetProfileResponseDto.kt +++ b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/GetProfileResponseDto.kt @@ -1,4 +1,4 @@ -package com.sampoom.android.feature.auth.data.remote.dto +package com.sampoom.android.feature.user.data.remote.dto data class GetProfileResponseDto( val userId: Long, @@ -11,4 +11,4 @@ data class GetProfileResponseDto( val organizationId: Long, val startedAt: String, val endedAt: String? -) +) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateProfileRequestDto.kt b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateProfileRequestDto.kt new file mode 100644 index 0000000..d87dc5a --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateProfileRequestDto.kt @@ -0,0 +1,5 @@ +package com.sampoom.android.feature.user.data.remote.dto + +data class UpdateProfileRequestDto( + val userName: String +) diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateProfileResponseDto.kt b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateProfileResponseDto.kt new file mode 100644 index 0000000..ba1b245 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/data/remote/dto/UpdateProfileResponseDto.kt @@ -0,0 +1,6 @@ +package com.sampoom.android.feature.user.data.remote.dto + +data class UpdateProfileResponseDto( + val userId: Long, + val userName: String +) diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt new file mode 100644 index 0000000..b629d67 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt @@ -0,0 +1,133 @@ +package com.sampoom.android.feature.user.data.repository + +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.sampoom.android.core.preferences.AuthPreferences +import com.sampoom.android.core.util.retry +import com.sampoom.android.feature.user.data.mapper.toModel +import com.sampoom.android.feature.user.data.paging.EmployeePagingSource +import com.sampoom.android.feature.user.data.remote.api.UserApi +import com.sampoom.android.feature.user.data.remote.dto.EditEmployeeRequestDto +import com.sampoom.android.feature.user.data.remote.dto.UpdateProfileRequestDto +import com.sampoom.android.feature.user.domain.model.Employee +import com.sampoom.android.feature.user.domain.model.User +import com.sampoom.android.feature.user.domain.repository.UserRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class UserRepositoryImpl @Inject constructor( + private val api: UserApi, + private val preferences: AuthPreferences, + private val pagingSourceFactory: EmployeePagingSource.Factory +) : UserRepository { + override suspend fun getStoredUser(): User? { + return preferences.getStoredUser() + } + + override suspend fun getProfile(workspace: String): Result { + return runCatching { + retry(times = 5, initialDelay = 300) { + val dto = api.getProfile(workspace) + if (!dto.success) throw Exception(dto.message) + val profileUser = dto.data.toModel() + val loginUser = preferences.getStoredUser() + + val completeUser = if (loginUser != null) { + User( + userId = profileUser.userId, + userName = profileUser.userName, + email = profileUser.email, + role = profileUser.role, + accessToken = loginUser.accessToken, // 저장된 토큰 + refreshToken = loginUser.refreshToken, // 저장된 토큰 + expiresIn = loginUser.expiresIn, // 저장된 토큰 + position = profileUser.position, + workspace = profileUser.workspace, + branch = profileUser.branch, + agencyId = profileUser.agencyId, + startedAt = profileUser.startedAt, + endedAt = profileUser.endedAt + ) + } else { + throw Exception() + } + + preferences.saveUser(completeUser) + completeUser + } + } + } + + override suspend fun updateProfile(user: User): Result { + return runCatching { + val requestDto = UpdateProfileRequestDto( + userName = user.userName + ) + val dto = api.updateProfile(requestDto) + if (!dto.success) throw Exception(dto.message) + val updatedProfile = dto.data.toModel() + val storedUser = preferences.getStoredUser() + val completeUser = if (storedUser != null) { + User( + userId = updatedProfile.userId, + userName = updatedProfile.userName, + email = user.email, + role = user.role, + accessToken = storedUser.accessToken, + refreshToken = storedUser.refreshToken, + expiresIn = storedUser.expiresIn, + position = user.position, + workspace = user.workspace, + branch = user.branch, + agencyId = user.agencyId, + startedAt = user.startedAt, + endedAt = user.endedAt + ) + } else throw Exception() + + preferences.saveUser(completeUser) + completeUser + } + } + + override fun getEmployeeList(): Flow> { + return Pager( + config = PagingConfig(pageSize = 20), + pagingSourceFactory = { pagingSourceFactory.create() } + ).flow + } + + override suspend fun editEmployee( + employee: Employee, + workspace: String + ): Result { + return runCatching { + val requestDto = EditEmployeeRequestDto( + position = employee.position.name + ) + val dto = api.editEmployee( + userId = employee.userId, + workspace = workspace, + body = requestDto + ) + if (!dto.success) throw Exception(dto.message) + + val updatedEmployee = dto.data.toModel() + val completeEmployee = Employee( + userId = updatedEmployee.userId, + email = employee.email, + role = employee.role, + userName = updatedEmployee.userName.takeIf { it.isNotBlank() } ?: employee.userName, + workspace = updatedEmployee.workspace.takeIf { it.isNotBlank() } ?: employee.workspace, + organizationId = employee.organizationId, + branch = employee.branch, + position = updatedEmployee.position, + startedAt = employee.startedAt, + endedAt = employee.endedAt + ) + + completeEmployee + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/di/UserModules.kt b/app/src/main/java/com/sampoom/android/feature/user/di/UserModules.kt new file mode 100644 index 0000000..3127f0f --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/di/UserModules.kt @@ -0,0 +1,26 @@ +package com.sampoom.android.feature.user.di + +import com.sampoom.android.feature.user.data.remote.api.UserApi +import com.sampoom.android.feature.user.data.repository.UserRepositoryImpl +import com.sampoom.android.feature.user.domain.repository.UserRepository +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import retrofit2.Retrofit +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class UserBinModule { + @Binds @Singleton + abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository +} + +@Module +@InstallIn(SingletonComponent::class) +object UserProvideModule { + @Provides @Singleton + fun provideUserApi(retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java) +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/domain/model/Employee.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/model/Employee.kt new file mode 100644 index 0000000..7c1a8b1 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/model/Employee.kt @@ -0,0 +1,16 @@ +package com.sampoom.android.feature.user.domain.model + +import com.sampoom.android.core.model.UserPosition + +data class Employee( + val userId: Long, + val email: String, + val role: String, + val userName: String, + val workspace: String, + val organizationId: Long, + val branch: String, + val position: UserPosition, + val startedAt: String?, + val endedAt: String? +) diff --git a/app/src/main/java/com/sampoom/android/feature/user/domain/model/EmployeeList.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/model/EmployeeList.kt new file mode 100644 index 0000000..45f8475 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/model/EmployeeList.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.user.domain.model + +data class EmployeeList( + val items: List, + val totalCount: Int = items.size, + val isEmpty: Boolean = items.isEmpty() +) { + companion object Companion { + fun empty() = EmployeeList(emptyList()) + } +} diff --git a/app/src/main/java/com/sampoom/android/feature/auth/domain/model/User.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/model/User.kt similarity index 87% rename from app/src/main/java/com/sampoom/android/feature/auth/domain/model/User.kt rename to app/src/main/java/com/sampoom/android/feature/user/domain/model/User.kt index 984500a..00abdf8 100644 --- a/app/src/main/java/com/sampoom/android/feature/auth/domain/model/User.kt +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/model/User.kt @@ -1,4 +1,4 @@ -package com.sampoom.android.feature.auth.domain.model +package com.sampoom.android.feature.user.domain.model import com.sampoom.android.core.model.UserPosition @@ -16,4 +16,4 @@ data class User( val agencyId: Long, val startedAt: String?, val endedAt: String? -) +) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/domain/repository/UserRepository.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/repository/UserRepository.kt new file mode 100644 index 0000000..6d826b3 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/repository/UserRepository.kt @@ -0,0 +1,14 @@ +package com.sampoom.android.feature.user.domain.repository + +import androidx.paging.PagingData +import com.sampoom.android.feature.user.domain.model.Employee +import com.sampoom.android.feature.user.domain.model.User +import kotlinx.coroutines.flow.Flow + +interface UserRepository { + suspend fun getStoredUser(): User? + suspend fun getProfile(workspace: String): Result + suspend fun updateProfile(user: User): Result + fun getEmployeeList(): Flow> + suspend fun editEmployee(employee: Employee, workspace: String): Result +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/EditEmployeeUseCase.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/EditEmployeeUseCase.kt new file mode 100644 index 0000000..d81d82d --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/EditEmployeeUseCase.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.user.domain.usecase + +import com.sampoom.android.feature.user.domain.model.Employee +import com.sampoom.android.feature.user.domain.repository.UserRepository +import javax.inject.Inject + +class EditEmployeeUseCase @Inject constructor( + private val repository: UserRepository +) { + suspend operator fun invoke(employee: Employee, workspace: String): Result = repository.editEmployee(employee, workspace) +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetEmployeeUseCase.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetEmployeeUseCase.kt new file mode 100644 index 0000000..542179e --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetEmployeeUseCase.kt @@ -0,0 +1,13 @@ +package com.sampoom.android.feature.user.domain.usecase + +import androidx.paging.PagingData +import com.sampoom.android.feature.user.domain.model.Employee +import com.sampoom.android.feature.user.domain.repository.UserRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetEmployeeUseCase @Inject constructor( + private val repository: UserRepository +) { + operator fun invoke(): Flow> = repository.getEmployeeList() +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetProfileUseCase.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetProfileUseCase.kt new file mode 100644 index 0000000..3b7e43c --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetProfileUseCase.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.user.domain.usecase + +import com.sampoom.android.feature.user.domain.model.User +import com.sampoom.android.feature.user.domain.repository.UserRepository +import javax.inject.Inject + +class GetProfileUseCase @Inject constructor( + private val repository: UserRepository +) { + suspend operator fun invoke(workspace: String): Result = repository.getProfile(workspace) +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetStoredUserUseCase.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetStoredUserUseCase.kt new file mode 100644 index 0000000..672956c --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetStoredUserUseCase.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.user.domain.usecase + +import com.sampoom.android.feature.user.domain.model.User +import com.sampoom.android.feature.user.domain.repository.UserRepository +import javax.inject.Inject + +class GetStoredUserUseCase @Inject constructor( + private val repository: UserRepository +) { + suspend operator fun invoke(): User? = repository.getStoredUser() +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/UpdateProfileUseCase.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/UpdateProfileUseCase.kt new file mode 100644 index 0000000..e3ed1e1 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/UpdateProfileUseCase.kt @@ -0,0 +1,11 @@ +package com.sampoom.android.feature.user.domain.usecase + +import com.sampoom.android.feature.user.domain.model.User +import com.sampoom.android.feature.user.domain.repository.UserRepository +import javax.inject.Inject + +class UpdateProfileUseCase @Inject constructor( + private val repository: UserRepository +) { + suspend operator fun invoke(user: User): Result = repository.updateProfile(user) +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeBottomSheet.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeBottomSheet.kt new file mode 100644 index 0000000..0b578ec --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeBottomSheet.kt @@ -0,0 +1,113 @@ +package com.sampoom.android.feature.user.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuAnchorType +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.sampoom.android.R +import com.sampoom.android.core.model.UserPosition +import com.sampoom.android.core.ui.component.CommonButton +import com.sampoom.android.core.ui.component.CommonTextField +import com.sampoom.android.core.util.positionToKorean +import com.sampoom.android.feature.user.domain.model.Employee + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EditEmployeeBottomSheet( + employee: Employee, + onDismiss: () -> Unit, + viewModel: EditEmployeeViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + var selectedPosition by rememberSaveable { mutableStateOf(employee.position) } + var positionMenuExpanded by remember { mutableStateOf(false) } + + val errorLabel = stringResource(R.string.common_error) + val editEmployeeLabel = stringResource(R.string.employee_edit_edited) + val deleteEmployeeLabel = stringResource(R.string.employee_edit_deleted) + + LaunchedEffect(employee) { + viewModel.onEvent(EditEmployeeUiEvent.Initialize(employee)) + selectedPosition = employee.position + } + + LaunchedEffect(errorLabel, editEmployeeLabel, deleteEmployeeLabel) { + viewModel.bindLabel(errorLabel, editEmployeeLabel, deleteEmployeeLabel) + } + + LaunchedEffect(uiState.isSuccess) { + if (uiState.isSuccess) { + viewModel.clearStatus() + onDismiss() + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 32.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + ExposedDropdownMenuBox( + expanded = positionMenuExpanded, + onExpandedChange = { positionMenuExpanded = it } + ) { + CommonTextField( + modifier = Modifier + .fillMaxWidth() + .menuAnchor( + type = ExposedDropdownMenuAnchorType.PrimaryNotEditable, + enabled = true + ), + readOnly = true, + value = positionToKorean(selectedPosition), + onValueChange = {}, + placeholder = stringResource(R.string.signup_placeholder_position), + singleLine = true + ) + ExposedDropdownMenu( + expanded = positionMenuExpanded, + onDismissRequest = { positionMenuExpanded = false } + ) { + UserPosition.entries.forEach { position -> + androidx.compose.material3.DropdownMenuItem( + text = { Text(positionToKorean(position)) }, + onClick = { + selectedPosition = position + positionMenuExpanded = false + } + ) + } + } + } + + Spacer(Modifier.height(8.dp)) + + CommonButton( + modifier = Modifier.fillMaxWidth(), + enabled = !uiState.isLoading && selectedPosition != employee.position, + onClick = { viewModel.onEvent(EditEmployeeUiEvent.EditEmployee(selectedPosition)) } + ) { + Text(stringResource(R.string.common_confirm)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeUiEvent.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeUiEvent.kt new file mode 100644 index 0000000..b8cf9e9 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeUiEvent.kt @@ -0,0 +1,10 @@ +package com.sampoom.android.feature.user.ui + +import com.sampoom.android.core.model.UserPosition +import com.sampoom.android.feature.user.domain.model.Employee + +interface EditEmployeeUiEvent { + data class Initialize(val employee: Employee) : EditEmployeeUiEvent + data class EditEmployee(val position: UserPosition) : EditEmployeeUiEvent + object Dismiss : EditEmployeeUiEvent +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeUiState.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeUiState.kt new file mode 100644 index 0000000..71a0597 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeUiState.kt @@ -0,0 +1,10 @@ +package com.sampoom.android.feature.user.ui + +import com.sampoom.android.feature.user.domain.model.Employee + +data class EditEmployeeUiState( + val employee: Employee? = null, + val isLoading: Boolean = false, + val error: String? = null, + val isSuccess: Boolean = false +) diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeViewModel.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeViewModel.kt new file mode 100644 index 0000000..a4a4df3 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/EditEmployeeViewModel.kt @@ -0,0 +1,105 @@ +package com.sampoom.android.feature.user.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sampoom.android.core.model.UserPosition +import com.sampoom.android.core.network.serverMessageOrNull +import com.sampoom.android.core.util.GlobalMessageHandler +import com.sampoom.android.feature.user.domain.usecase.EditEmployeeUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class EditEmployeeViewModel @Inject constructor( + private val messageHandler: GlobalMessageHandler, + private val editEmployeeUseCase: EditEmployeeUseCase +) : ViewModel() { + + private companion object { + private const val TAG = "EditEmployeeViewModel" + } + + private val _uiState = MutableStateFlow(EditEmployeeUiState()) + val uiState: StateFlow = _uiState + + private var errorLabel: String = "" + private var editEmployeeLabel: String = "" + private var deleteEmployeeLabel: String = "" + + fun bindLabel(error: String, editEmployee: String, deleteEmployee: String) { + errorLabel = error + editEmployeeLabel = editEmployee + deleteEmployeeLabel = deleteEmployee + } + + fun onEvent(event: EditEmployeeUiEvent) { + when (event) { + is EditEmployeeUiEvent.Initialize -> { + _uiState.update { + it.copy( + employee = event.employee, + isLoading = false, + isSuccess = false + ) + } + } + is EditEmployeeUiEvent.EditEmployee -> { + editEmployee(event.position) + } + is EditEmployeeUiEvent.Dismiss -> { + _uiState.update { + it.copy( + employee = null, + isLoading = false, + error = null + ) + } + } + } + } + + private fun editEmployee(newPosition: UserPosition) { + viewModelScope.launch { + val currentEmployee = _uiState.value.employee ?: run { + messageHandler.showMessage(message = errorLabel, isError = true) + return@launch + } + + val updateEmployee = currentEmployee.copy(position = newPosition) + _uiState.update { it.copy(isLoading = true, error = null) } + + editEmployeeUseCase(updateEmployee, "AGENCY") + .onSuccess { employee -> + _uiState.update { + it.copy( + employee = employee, + isLoading = false, + error = null, + isSuccess = true + ) + } + messageHandler.showMessage(message = editEmployeeLabel, isError = false) + } + .onFailure { throwable -> + val backendMessage = throwable.serverMessageOrNull() + val error = backendMessage ?: (throwable.message ?: errorLabel) + messageHandler.showMessage(message = error, isError = true) + + _uiState.update { + it.copy( + isLoading = false, + error = error + ) + } + } + } + } + + fun clearStatus() { + _uiState.update { it.copy(isSuccess = false, error = null) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt new file mode 100644 index 0000000..92d1ff6 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt @@ -0,0 +1,339 @@ +package com.sampoom.android.feature.user.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.paging.LoadState +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemKey +import com.sampoom.android.R +import com.sampoom.android.core.ui.component.ButtonSize +import com.sampoom.android.core.ui.component.ButtonVariant +import com.sampoom.android.core.ui.component.CommonButton +import com.sampoom.android.core.ui.component.EmptyContent +import com.sampoom.android.core.ui.component.ErrorContent +import com.sampoom.android.core.ui.theme.FailRed +import com.sampoom.android.core.ui.theme.backgroundCardColor +import com.sampoom.android.core.ui.theme.textColor +import com.sampoom.android.core.ui.theme.textSecondaryColor +import com.sampoom.android.core.util.formatDate +import com.sampoom.android.core.util.positionToKorean +import com.sampoom.android.feature.user.domain.model.Employee +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EmployeeListScreen( + onNavigateBack: () -> Unit = {}, + viewModel: EmployeeListViewModel = hiltViewModel() +) { + val coroutineScope = rememberCoroutineScope() + val errorLabel = stringResource(R.string.common_error) + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val employeeListPaged = viewModel.employeeListPaged.collectAsLazyPagingItems() + val pullRefreshState = rememberPullToRefreshState() + val listState = rememberSaveable(saver = LazyListState.Saver) { LazyListState() } + val sheetState = rememberModalBottomSheetState(true) + val selectedEmployee = uiState.selectedEmployee + + LaunchedEffect(errorLabel) { + viewModel.bindLabel(errorLabel) + } + + LaunchedEffect(Unit) { + employeeListPaged.refresh() + } + + LaunchedEffect(selectedEmployee) { + if (selectedEmployee != null && !sheetState.isVisible) { + sheetState.show() + } else if (selectedEmployee == null && sheetState.isVisible) { + sheetState.hide() + } + } + + PullToRefreshBox( + isRefreshing = false, + onRefresh = { employeeListPaged.refresh() }, + state = pullRefreshState, + modifier = Modifier.fillMaxSize(), + indicator = { + Indicator( + modifier = Modifier.align(Alignment.TopCenter), + isRefreshing = uiState.employeeLoading, + containerColor = MaterialTheme.colorScheme.primaryContainer, + color = MaterialTheme.colorScheme.onPrimaryContainer, + state = pullRefreshState + ) + } + ) { + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.employee_title)) }, + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon( + painter = painterResource(R.drawable.ic_arrow_back), + contentDescription = stringResource(R.string.nav_back) + ) + } + } + ) + } + ) { innerPadding -> + when (employeeListPaged.loadState.refresh) { + is LoadState.Loading -> { + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + is LoadState.Error -> { + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + contentAlignment = Alignment.Center + ) { + ErrorContent( + onRetry = { viewModel.onEvent(EmployeeListUiEvent.RetryEmployeeList) }, + modifier = Modifier.height(200.dp) + ) + } + } + + else -> { + if (employeeListPaged.loadState.refresh !is LoadState.Loading && employeeListPaged.itemCount == 0) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + EmptyContent( + message = stringResource(R.string.employee_empty_employee), + modifier = Modifier.height(200.dp) + ) + } + } else { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + items( + count = employeeListPaged.itemCount, + key = employeeListPaged.itemKey { it.userId } + ) { index -> + val employee = employeeListPaged[index] + if (employee != null) { + EmployeeListItemCard( + employee = employee, + onDeleteClick = { + + }, + onEditClick = { + viewModel.onEvent(EmployeeListUiEvent.ShowBottomSheet(employee)) + } + ) + } + } + + // 로딩 상태 처리 + item { + when (employeeListPaged.loadState.append) { + is LoadState.Loading -> { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + is LoadState.Error -> { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(R.string.common_error), + color = FailRed + ) + } + } + + else -> {} + } + } + + item { Spacer(Modifier.height(100.dp)) } + } + } + } + } + } + } + + if (selectedEmployee != null) { + uiState.selectedEmployee?.let { selectedEmployee -> + ModalBottomSheet( + onDismissRequest = { + coroutineScope.launch { + viewModel.onEvent(EmployeeListUiEvent.DismissBottomSheet) + sheetState.hide() + } + }, + sheetState = sheetState + ) { + EditEmployeeBottomSheet( + employee = selectedEmployee, + onDismiss = { + coroutineScope.launch { + viewModel.onEvent(EmployeeListUiEvent.DismissBottomSheet) + sheetState.hide() + } + } + ) + } + } + } +} + +@Composable +private fun EmployeeListItemCard( + employee: Employee, + onDeleteClick: () -> Unit, + onEditClick: () -> Unit +) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = backgroundCardColor()) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = employee.userName, + color = textColor(), + style = MaterialTheme.typography.titleLarge + ) + Text( + text = positionToKorean(employee.position), + color = textSecondaryColor(), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Light + ) + + Spacer(Modifier.height(8.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.employee_email), + color = textSecondaryColor(), + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = employee.email, + color = textSecondaryColor(), + style = MaterialTheme.typography.bodyMedium + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.employee_startedAt), + color = textSecondaryColor(), + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = formatDate(employee.startedAt ?: stringResource(R.string.common_slash)), + color = textSecondaryColor(), + style = MaterialTheme.typography.bodyMedium + ) + } + + Spacer(Modifier.height(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth() + ) { + CommonButton( + modifier = Modifier.weight(1F), + variant = ButtonVariant.Error, + size = ButtonSize.Large, + onClick = { onDeleteClick() } + ) { + Text(stringResource(R.string.employee_delete)) + } + Spacer(Modifier.width(8.dp)) + CommonButton( + modifier = Modifier.weight(1F), + variant = ButtonVariant.Primary, + size = ButtonSize.Large, + onClick = { onEditClick() } + ) { + Text(stringResource(R.string.employee_edit)) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListUiEvent.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListUiEvent.kt new file mode 100644 index 0000000..eabb110 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListUiEvent.kt @@ -0,0 +1,10 @@ +package com.sampoom.android.feature.user.ui + +import com.sampoom.android.feature.user.domain.model.Employee + +interface EmployeeListUiEvent { + object LoadEmployeeList : EmployeeListUiEvent + object RetryEmployeeList : EmployeeListUiEvent + data class ShowBottomSheet(val employee: Employee) : EmployeeListUiEvent + object DismissBottomSheet : EmployeeListUiEvent +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListUiState.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListUiState.kt new file mode 100644 index 0000000..648efbf --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListUiState.kt @@ -0,0 +1,10 @@ +package com.sampoom.android.feature.user.ui + +import com.sampoom.android.feature.user.domain.model.Employee + +data class EmployeeListUiState( + val employeeList: List = emptyList(), + val employeeLoading: Boolean = false, + val employeeError: String? = null, + val selectedEmployee: Employee? = null +) diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListViewModel.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListViewModel.kt new file mode 100644 index 0000000..0383392 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListViewModel.kt @@ -0,0 +1,47 @@ +package com.sampoom.android.feature.user.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn +import com.sampoom.android.core.util.GlobalMessageHandler +import com.sampoom.android.feature.user.domain.model.Employee +import com.sampoom.android.feature.user.domain.usecase.GetEmployeeUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject + +@HiltViewModel +class EmployeeListViewModel @Inject constructor( + private val messageHandler: GlobalMessageHandler, + private val getEmployeeUseCase: GetEmployeeUseCase +) : ViewModel() { + + private companion object { + private const val TAG = "EmployeeListViewModel" + } + + private val _uiState = MutableStateFlow(EmployeeListUiState()) + val uiState: StateFlow = _uiState + + private var errorLabel: String = "" + + fun bindLabel(error: String) { + errorLabel = error + } + + val employeeListPaged : Flow> = getEmployeeUseCase() + .cachedIn(viewModelScope) + + fun onEvent(event: EmployeeListUiEvent) { + when (event) { + is EmployeeListUiEvent.LoadEmployeeList -> {} + is EmployeeListUiEvent.RetryEmployeeList -> {} + is EmployeeListUiEvent.ShowBottomSheet -> _uiState.update { it.copy(selectedEmployee = event.employee) } + is EmployeeListUiEvent.DismissBottomSheet -> _uiState.update { it.copy(selectedEmployee = null) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileBottomSheet.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileBottomSheet.kt new file mode 100644 index 0000000..1c836f2 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileBottomSheet.kt @@ -0,0 +1,84 @@ +package com.sampoom.android.feature.user.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.sampoom.android.R +import com.sampoom.android.core.ui.component.CommonButton +import com.sampoom.android.core.ui.component.CommonTextField +import com.sampoom.android.feature.user.domain.model.User + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun UpdateProfileBottomSheet( + user: User, + onDismiss: () -> Unit, + onProfileUpdated: (User) -> Unit = {}, + viewModel: UpdateProfileViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + var userName by rememberSaveable { mutableStateOf(user.userName) } + + val errorLabel = stringResource(R.string.common_error) + val updateProfileLabel = stringResource(R.string.setting_edit_profile_edited) + + LaunchedEffect(user) { + viewModel.onEvent(UpdateProfileUiEvent.Initialize(user)) + userName = user.userName + } + + LaunchedEffect(errorLabel, updateProfileLabel) { + viewModel.bindLabel(errorLabel, updateProfileLabel) + } + + LaunchedEffect(uiState.isSuccess) { + if (uiState.isSuccess) { + val updatedUser = uiState.user + if (updatedUser != null) onProfileUpdated(updatedUser) + viewModel.clearStatus() + onDismiss() + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 32.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + CommonTextField( + modifier = Modifier.fillMaxWidth(), + value = userName, + onValueChange = { userName = it }, + placeholder = stringResource(R.string.setting_edit_profile_placeholder_username), + singleLine = true + ) + + Spacer(Modifier.height(8.dp)) + + CommonButton( + modifier = Modifier.fillMaxWidth(), + enabled = !uiState.isLoading && userName.isNotBlank() && userName != user.userName, + onClick = { viewModel.onEvent(UpdateProfileUiEvent.UpdateProfile(userName)) } + ) { + Text(stringResource(R.string.setting_edit_profile)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileUiEvent.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileUiEvent.kt new file mode 100644 index 0000000..d58c24e --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileUiEvent.kt @@ -0,0 +1,9 @@ +package com.sampoom.android.feature.user.ui + +import com.sampoom.android.feature.user.domain.model.User + +interface UpdateProfileUiEvent { + data class Initialize(val user: User) : UpdateProfileUiEvent + data class UpdateProfile(val userName: String) : UpdateProfileUiEvent + object Dismiss : UpdateProfileUiEvent +} \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileUiState.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileUiState.kt new file mode 100644 index 0000000..93aa73f --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileUiState.kt @@ -0,0 +1,10 @@ +package com.sampoom.android.feature.user.ui + +import com.sampoom.android.feature.user.domain.model.User + +data class UpdateProfileUiState( + val user: User? = null, + val isLoading: Boolean = false, + val error: String? = null, + val isSuccess: Boolean = false +) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileViewModel.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileViewModel.kt new file mode 100644 index 0000000..83d91a1 --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/UpdateProfileViewModel.kt @@ -0,0 +1,100 @@ +package com.sampoom.android.feature.user.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sampoom.android.core.network.serverMessageOrNull +import com.sampoom.android.core.util.GlobalMessageHandler +import com.sampoom.android.feature.user.domain.usecase.UpdateProfileUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class UpdateProfileViewModel @Inject constructor( + private val messageHandler: GlobalMessageHandler, + private val updateProfileUseCase: UpdateProfileUseCase +) : ViewModel() { + + private companion object Companion { + private const val TAG = "UpdateProfileViewModel" + } + + private val _uiState = MutableStateFlow(UpdateProfileUiState()) + val uiState: StateFlow = _uiState + + private var errorLabel: String = "" + private var updateProfileLabel: String = "" + + fun bindLabel(error: String, updateProfile: String) { + errorLabel = error + updateProfileLabel = updateProfile + } + + fun onEvent(event: UpdateProfileUiEvent) { + when (event) { + is UpdateProfileUiEvent.Initialize -> { + _uiState.update { + it.copy( + user = event.user, + isLoading = false, + isSuccess = false + ) + } + } + is UpdateProfileUiEvent.UpdateProfile -> { + updateProfile(event.userName) + } + is UpdateProfileUiEvent.Dismiss -> { + _uiState.update { + it.copy( + user = null + ) + } + } + } + } + + private fun updateProfile(newUserName: String) { + viewModelScope.launch { + val currentUser = _uiState.value.user ?: run { + messageHandler.showMessage(message = errorLabel, isError = true) + return@launch + } + + val updatedUser = currentUser.copy(userName = newUserName) + _uiState.update { it.copy(isLoading = true, error = null) } + + updateProfileUseCase(updatedUser) + .onSuccess { user -> + _uiState.update { + it.copy( + user = user, + isLoading = false, + error = null, + isSuccess = true + ) + } + messageHandler.showMessage(message = updateProfileLabel, isError = false) + } + .onFailure { throwable -> + val backendMessage = throwable.serverMessageOrNull() + val error = backendMessage ?: (throwable.message ?: errorLabel) + messageHandler.showMessage(message = error, isError = true) + + _uiState.update { + it.copy( + isLoading = false, + error = error + ) + } + } + } + } + + fun clearStatus() { + _uiState.update { it.copy(isSuccess = false, error = null) } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1001f02..0f18d62 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -114,6 +114,16 @@ 입고 처리되었습니다 총 가격 + + 직원관리 + 직원이 없습니다. + 이메일 + 가입일 + 삭제 + 수정 + 직원 수정이 완료되었습니다. + 직원 삭제가 완료되었습니다. + 대기중 주문확인 @@ -127,6 +137,8 @@ 설정 프로필 수정 + 이름 입력 + 프로필 수정이 완료되었습니다. 로그아웃 로그아웃 하시겠습니까? diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1a524e..653678d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ materialIconsCore = "1.7.8" navigationCompose = "2.9.5" retrofitVersion = "3.0.0" paging = "3.3.6" +material3 = "1.4.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -51,6 +52,7 @@ androidx-paging-runtime = { group = "androidx.paging", name = "paging-runtime", androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" } logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofitVersion" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 592da0ea249cdee076ac6c0edcc37e6f62cae6d3 Mon Sep 17 00:00:00 2001 From: Sangyoon Date: Sat, 8 Nov 2025 04:35:57 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[FIX]=20=EC=BD=94=EB=93=9C=20=EB=9E=98?= =?UTF-8?q?=EB=B9=97=20=EB=A6=AC=EB=B7=B0=20=EC=82=AC=ED=95=AD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 - .../com/sampoom/android/feature/user/ui/EmployeeListScreen.kt | 2 +- gradle/libs.versions.toml | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4bf2b53..7d05d41 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -69,7 +69,6 @@ kotlin { dependencies { // hilt implementation(libs.hilt.android) - implementation(libs.androidx.material3) ksp(libs.hilt.android.compiler) implementation(libs.androidx.hilt.lifecycle.viewmodel.compose) implementation(libs.androidx.hilt.navigation.compose) diff --git a/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt index 92d1ff6..204d2fb 100644 --- a/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/user/ui/EmployeeListScreen.kt @@ -140,7 +140,7 @@ fun EmployeeListScreen( contentAlignment = Alignment.Center ) { ErrorContent( - onRetry = { viewModel.onEvent(EmployeeListUiEvent.RetryEmployeeList) }, + onRetry = { employeeListPaged.refresh() }, modifier = Modifier.height(200.dp) ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 653678d..a1a524e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,6 @@ materialIconsCore = "1.7.8" navigationCompose = "2.9.5" retrofitVersion = "3.0.0" paging = "3.3.6" -material3 = "1.4.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -52,7 +51,6 @@ androidx-paging-runtime = { group = "androidx.paging", name = "paging-runtime", androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" } logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofitVersion" } -androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 96205fb09102fb9f9e11488729a2b6a612f335ff Mon Sep 17 00:00:00 2001 From: Sangyoon Date: Sat, 8 Nov 2025 21:34:44 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[FEAT]=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EC=A7=81=EC=9B=90=20=EC=88=98=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/dashboard/ui/DashboardScreen.kt | 7 +++- .../feature/dashboard/ui/DashboardUiState.kt | 3 ++ .../dashboard/ui/DashboardViewModel.kt | 37 ++++++++++++++++++- .../data/repository/UserRepositoryImpl.kt | 18 +++++++++ .../user/domain/repository/UserRepository.kt | 1 + .../domain/usecase/GetEmployeeCountUseCase.kt | 10 +++++ 6 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetEmployeeCountUseCase.kt diff --git a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt index 8fc4a11..6586893 100644 --- a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardScreen.kt @@ -1,5 +1,6 @@ package com.sampoom.android.feature.dashboard.ui +import android.R.attr.onClick import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -155,6 +156,7 @@ fun DashboardScreen( ButtonSection( isManager = isManager, dashboard = uiState.dashboard, + employeeCount = uiState.employeeCount, onEmployeeClick = { onEmployeeClick() } ) } @@ -230,7 +232,8 @@ fun TitleSection( fun ButtonSection( onEmployeeClick: () -> Unit, isManager: Boolean, - dashboard: Dashboard? + dashboard: Dashboard?, + employeeCount: Int? ) { Column( modifier = Modifier @@ -247,7 +250,7 @@ fun ButtonSection( ), painter = painterResource(R.drawable.employee), painterDescription = stringResource(R.string.dashboard_employee), - text = 45.toString(), // TODO : API 연동 + text = employeeCount?.toString() ?: stringResource(R.string.common_slash), subText = stringResource(R.string.dashboard_employee), onClick = { onEmployeeClick() } ) diff --git a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardUiState.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardUiState.kt index 4986b22..cd5733a 100644 --- a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardUiState.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardUiState.kt @@ -12,4 +12,7 @@ data class DashboardUiState( val dashboardError: String? = null, val weeklySummaryLoading: Boolean = false, val weeklySummaryError: String? = null, + val employeeCount: Int? = null, + val employeeCountLoading: Boolean = false, + val employeeCountError: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardViewModel.kt b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardViewModel.kt index afc5613..eb463b6 100644 --- a/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardViewModel.kt +++ b/app/src/main/java/com/sampoom/android/feature/dashboard/ui/DashboardViewModel.kt @@ -11,6 +11,8 @@ import com.sampoom.android.feature.dashboard.domain.usecase.WeeklySummaryUseCase import com.sampoom.android.feature.order.domain.model.Order import com.sampoom.android.feature.order.domain.usecase.GetOrderUseCase import com.sampoom.android.feature.user.domain.model.User +import com.sampoom.android.feature.user.domain.usecase.GetEmployeeCountUseCase +import com.sampoom.android.feature.user.domain.usecase.GetEmployeeUseCase import com.sampoom.android.feature.user.domain.usecase.GetStoredUserUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow @@ -26,7 +28,8 @@ class DashboardViewModel @Inject constructor( private val getOrderListUseCase: GetOrderUseCase, private val getStoredUserUseCase: GetStoredUserUseCase, private val getDashboardUseCase: GetDashboardUseCase, - private val getWeeklySummaryUseCase: WeeklySummaryUseCase + private val getWeeklySummaryUseCase: WeeklySummaryUseCase, + private val getEmployeeCountUseCase: GetEmployeeCountUseCase ): ViewModel() { private companion object { @@ -51,6 +54,7 @@ class DashboardViewModel @Inject constructor( init { loadDashboard() loadWeeklySummary() + loadEmployeeCount() viewModelScope.launch { _user.value = getStoredUserUseCase() } @@ -61,10 +65,12 @@ class DashboardViewModel @Inject constructor( is DashboardUiEvent.LoadDashboard -> { loadDashboard() loadWeeklySummary() + loadEmployeeCount() } is DashboardUiEvent.RetryDashboard -> { loadDashboard() loadWeeklySummary() + loadEmployeeCount() } } } @@ -127,6 +133,35 @@ class DashboardViewModel @Inject constructor( } } + private fun loadEmployeeCount() { + viewModelScope.launch { + _uiState.update { it.copy(employeeCountLoading = true, employeeCountError = null) } + + getEmployeeCountUseCase() + .onSuccess { count -> + _uiState.update { + it.copy( + employeeCount = count, + employeeCountLoading = false, + employeeCountError = null + ) + } + } + .onFailure { throwable -> + val backendMessage = throwable.serverMessageOrNull() + val error = backendMessage ?: (throwable.message ?: errorLabel) + messageHandler.showMessage(message = error, isError = true) + + _uiState.update { + it.copy( + weeklySummaryLoading = false, + weeklySummaryError = error + ) + } + } + } + } + fun refreshUser() { viewModelScope.launch { _user.value = getStoredUserUseCase() diff --git a/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt index b629d67..0f1dfb5 100644 --- a/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/sampoom/android/feature/user/data/repository/UserRepositoryImpl.kt @@ -130,4 +130,22 @@ class UserRepositoryImpl @Inject constructor( completeEmployee } } + + override suspend fun getEmployeeCount(): Result { + return runCatching { + val user = preferences.getStoredUser() ?: throw Exception() + val workspace = user.workspace + val organizationId = user.agencyId + + val dto = api.getEmployeeList( + workspace = workspace, + organizationId = organizationId, + page = 0, + size = 1 + ) + + if (!dto.success) throw Exception(dto.message) + dto.data.meta.totalElements + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/domain/repository/UserRepository.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/repository/UserRepository.kt index 6d826b3..52c64c3 100644 --- a/app/src/main/java/com/sampoom/android/feature/user/domain/repository/UserRepository.kt +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/repository/UserRepository.kt @@ -11,4 +11,5 @@ interface UserRepository { suspend fun updateProfile(user: User): Result fun getEmployeeList(): Flow> suspend fun editEmployee(employee: Employee, workspace: String): Result + suspend fun getEmployeeCount(): Result } \ No newline at end of file diff --git a/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetEmployeeCountUseCase.kt b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetEmployeeCountUseCase.kt new file mode 100644 index 0000000..c8bf5ed --- /dev/null +++ b/app/src/main/java/com/sampoom/android/feature/user/domain/usecase/GetEmployeeCountUseCase.kt @@ -0,0 +1,10 @@ +package com.sampoom.android.feature.user.domain.usecase + +import com.sampoom.android.feature.user.domain.repository.UserRepository +import javax.inject.Inject + +class GetEmployeeCountUseCase @Inject constructor( + private val repository: UserRepository +) { + suspend operator fun invoke(): Result = repository.getEmployeeCount() +} \ No newline at end of file