diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ShareAccountRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ShareAccountRepository.kt index cfa36d57336..09141e5d63c 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ShareAccountRepository.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/ShareAccountRepository.kt @@ -15,5 +15,5 @@ import kotlinx.coroutines.flow.Flow interface ShareAccountRepository { - fun getShareTemplate(clientId: Int): Flow> + fun getShareTemplate(clientId: Int, productId: Int?): Flow> } diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ShareAccountRepositoryImpl.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ShareAccountRepositoryImpl.kt index 238390cf8f6..77d0c837325 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ShareAccountRepositoryImpl.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/ShareAccountRepositoryImpl.kt @@ -20,7 +20,7 @@ class ShareAccountRepositoryImpl( private val dataManagerShare: DataManagerShare, ) : ShareAccountRepository { - override fun getShareTemplate(clientId: Int): Flow> { - return dataManagerShare.getShareTemplate(clientId).asDataStateFlow() + override fun getShareTemplate(clientId: Int, productId: Int?): Flow> { + return dataManagerShare.getShareTemplate(clientId, productId).asDataStateFlow() } } diff --git a/core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosOutlinedTextField.kt b/core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosOutlinedTextField.kt index 64fb1a74270..8c5da4d6551 100644 --- a/core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosOutlinedTextField.kt +++ b/core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/component/MifosOutlinedTextField.kt @@ -368,6 +368,7 @@ fun MifosDatePickerTextField( .fillMaxWidth() .clip(DesignToken.shapes.medium), label: String? = null, + errorMessage: String? = null, openDatePicker: () -> Unit, ) { OutlinedTextField( @@ -398,6 +399,16 @@ fun MifosDatePickerTextField( Icon(imageVector = Icons.Default.CalendarMonth, null) } }, + isError = errorMessage != null, + supportingText = errorMessage?.let { + { + Text( + text = it, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error, + ) + } + }, ) } diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerShare.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerShare.kt index d2fc0bd0c50..e7f0a9d9391 100644 --- a/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerShare.kt +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerShare.kt @@ -17,6 +17,6 @@ class DataManagerShare( private val baseApiManager: BaseApiManager, ) { - fun getShareTemplate(clientId: Int): Flow = - baseApiManager.shareAccountService.shareProductTemplate(clientId) + fun getShareTemplate(clientId: Int, productId: Int?): Flow = + baseApiManager.shareAccountService.shareProductTemplate(clientId, productId) } diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ProductOption.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ProductOption.kt index 3f101dc485a..e70fa749811 100644 --- a/core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ProductOption.kt +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ProductOption.kt @@ -9,20 +9,34 @@ */ package com.mifos.core.network.model.share -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class ProductOption( - @SerialName("id") val id: Int, - @SerialName("name") val name: String, - @SerialName("shortName") val shortName: String, - @SerialName("totalShares") val totalShares: Int, + + val currency: ProductCurrency? = null, + + val unitPrice: Double? = null, +) + +@Serializable +data class ProductCurrency( + val code: String, + + val name: String, + + val decimalPlaces: Int? = null, + + val displaySymbol: String? = null, + + val nameCode: String? = null, + + val displayLabel: String? = null, ) diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareTemplate.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareTemplate.kt index 71599ff8b28..902dcfbc9e4 100644 --- a/core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareTemplate.kt +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/model/share/ShareTemplate.kt @@ -9,17 +9,46 @@ */ package com.mifos.core.network.model.share +import com.mifos.core.model.objects.template.client.Currency import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class ShareTemplate( - @SerialName("clientId") val clientId: Int, - @SerialName(value = "clientName") val clientName: String, - @SerialName("productOptions") + val currency: Currency? = null, + + val currentMarketPrice: Double? = null, + val productOptions: List = emptyList(), + + @SerialName("clientSavingsAccounts") + val savingsAccountOptions: List? = emptyList(), + + val lockinPeriodFrequencyTypeOptions: List? = emptyList(), + + val minimumActivePeriodFrequencyTypeOptions: List? = emptyList(), +) + +@Serializable +data class SavingsAccountOption( + val id: Int, + + val accountNo: String, + + val savingsProductName: String? = null, + + val savingsProductId: Int? = null, +) + +@Serializable +data class FrequencyTypeOption( + val id: Int, + + val code: String, + + val value: String, ) diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/services/ShareAccountService.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/services/ShareAccountService.kt index dd9c5241e62..cf931b76b35 100644 --- a/core/network/src/commonMain/kotlin/com/mifos/core/network/services/ShareAccountService.kt +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/services/ShareAccountService.kt @@ -20,5 +20,6 @@ interface ShareAccountService { @GET("accounts/" + APIEndPoint.SHARE + "/template") fun shareProductTemplate( @Query("clientId") clientId: Int, + @Query("productId") productId: Int?, ): Flow } diff --git a/feature/client/src/commonMain/composeResources/values/strings.xml b/feature/client/src/commonMain/composeResources/values/strings.xml index b26e3821755..8fdc6ace0da 100644 --- a/feature/client/src/commonMain/composeResources/values/strings.xml +++ b/feature/client/src/commonMain/composeResources/values/strings.xml @@ -539,21 +539,33 @@ New Fixed Deposit Account - Details - Terms - Charges - Preview - Back - Next - - Product Name* - Submission Date* - External Id - Select - Cancel + Details + Terms + Charges + Preview + Back + Next + + Product Name* + Submission Date* + External Id + Select + Cancel + + Currency + Current Price + Total Number of Shares + Default Savings Account + Application Date + Allow dividends for inactive clients + Minimum Active Period + Frequency + Type + Lock-in Period + - This field cannot be empty + This field cannot be empty Update diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountScreen.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountScreen.kt index 5af3e6e676a..7bc5ba55b3f 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountScreen.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountScreen.kt @@ -10,10 +10,10 @@ package com.mifos.feature.client.createShareAccount import androidclient.feature.client.generated.resources.Res -import androidclient.feature.client.generated.resources.share_account_charges -import androidclient.feature.client.generated.resources.share_account_details -import androidclient.feature.client.generated.resources.share_account_preview -import androidclient.feature.client.generated.resources.share_account_terms +import androidclient.feature.client.generated.resources.feature_share_account_charges +import androidclient.feature.client.generated.resources.feature_share_account_details +import androidclient.feature.client.generated.resources.feature_share_account_preview +import androidclient.feature.client.generated.resources.feature_share_account_terms import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -71,23 +71,24 @@ private fun CreateShareAccountContent( navController: NavController, ) { val steps = listOf( - Step(name = stringResource(Res.string.share_account_details)) { + Step(name = stringResource(Res.string.feature_share_account_details)) { DetailsPage( state = state, onAction = onAction, ) }, - Step(name = stringResource(Res.string.share_account_terms)) { + Step(name = stringResource(Res.string.feature_share_account_terms)) { TermsPage( - onNext = { onAction(ShareAccountAction.NextStep) }, + state = state, + onAction = onAction, ) }, - Step(name = stringResource(Res.string.share_account_charges)) { + Step(name = stringResource(Res.string.feature_share_account_charges)) { ChargesPage( onNext = { onAction(ShareAccountAction.NextStep) }, ) }, - Step(name = stringResource(Res.string.share_account_preview)) { + Step(name = stringResource(Res.string.feature_share_account_preview)) { PreviewPage( onNext = { onAction(ShareAccountAction.Finish) }, ) diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt index bb81f42ac3e..a9251042d8f 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/CreateShareAccountViewModel.kt @@ -18,7 +18,9 @@ import com.mifos.core.common.utils.DataState import com.mifos.core.common.utils.DateHelper import com.mifos.core.data.repository.ShareAccountRepository import com.mifos.core.data.util.NetworkMonitor +import com.mifos.core.network.model.share.FrequencyTypeOption import com.mifos.core.network.model.share.ProductOption +import com.mifos.core.network.model.share.SavingsAccountOption import com.mifos.core.ui.util.BaseViewModel import com.mifos.core.ui.util.TextFieldsValidator import kotlinx.coroutines.flow.first @@ -43,11 +45,57 @@ class CreateShareAccountViewModel( loadShareTemplate(route.clientId) } + private fun loadShareTemplateFromProduct(client: Int, productId: Int) { + viewModelScope.launch { + val online = networkMonitor.isOnline.first() + if (online) { + repository.getShareTemplate(client, productId).collect { dataState -> + when (dataState) { + is DataState.Error -> { + mutableStateFlow.update { + it.copy( + screenState = ShareAccountState.ScreenState.Error(dataState.message), + ) + } + } + + DataState.Loading -> { + mutableStateFlow.update { + it.copy( + screenState = ShareAccountState.ScreenState.Loading, + ) + } + } + + is DataState.Success -> { + mutableStateFlow.update { + it.copy( + screenState = ShareAccountState.ScreenState.Success, + currency = dataState.data.currency?.name, + currentPrice = dataState.data.currentMarketPrice?.toString(), + savingsAccountOptions = dataState.data.savingsAccountOptions.orEmpty(), + lockInPeriodFrequencyTypeOptions = dataState.data.lockinPeriodFrequencyTypeOptions.orEmpty(), + minimumActivePeriodFrequencyTypeOptions = dataState.data.minimumActivePeriodFrequencyTypeOptions.orEmpty(), + ) + } + } + } + } + } else { + mutableStateFlow.update { + it.copy( + screenState = ShareAccountState.ScreenState.Error(getString(Res.string.feature_client_error_network_not_available)), + ) + } + } + } + } + private fun loadShareTemplate(client: Int) { viewModelScope.launch { val online = networkMonitor.isOnline.first() if (online) { - repository.getShareTemplate(client).collect { dataState -> + repository.getShareTemplate(client, null).collect { dataState -> when (dataState) { is DataState.Error -> { mutableStateFlow.update { @@ -113,6 +161,55 @@ class CreateShareAccountViewModel( ) } } else { + state.selectedProduct?.id?.let { productId -> + loadShareTemplateFromProduct(client = route.clientId, productId = productId) + } + moveToNextStep() + } + } + + private fun handleOnTermsNext() { + var hasError = false + var newState = state + + val totalSharesError = TextFieldsValidator.numberValidator(state.totalShares) + if (totalSharesError != null) { + newState = newState.copy(totalSharesError = totalSharesError) + hasError = true + } + + if (state.savingsAccountIdx == null) { + newState = newState.copy(savingsAccountError = TextFieldsValidator.stringValidator("")) + hasError = true + } + + if (state.minActivePeriodFreq.isNotBlank()) { + val freqError = TextFieldsValidator.numberValidator(state.minActivePeriodFreq) + if (freqError != null) { + newState = newState.copy(minActivePeriodFreqError = freqError) + hasError = true + } + if (state.minActivePeriodFreqTypeIdx == null) { + newState = newState.copy(minActivePeriodFreqTypeError = TextFieldsValidator.stringValidator("")) + hasError = true + } + } + + if (state.lockInPeriodFreq.isNotBlank()) { + val freqError = TextFieldsValidator.numberValidator(state.lockInPeriodFreq) + if (freqError != null) { + newState = newState.copy(lockInPeriodFreqError = freqError) + hasError = true + } + if (state.lockInPeriodFreqTypeIdx == null) { + newState = newState.copy(lockInPeriodFreqTypeError = TextFieldsValidator.stringValidator("")) + hasError = true + } + } + + mutableStateFlow.update { newState } + + if (!hasError) { moveToNextStep() } } @@ -122,6 +219,7 @@ class CreateShareAccountViewModel( ShareAccountAction.NextStep -> { moveToNextStep() } + is ShareAccountAction.OnStepChange -> { mutableStateFlow.update { it.copy(currentStep = action.index) } } @@ -134,7 +232,7 @@ class CreateShareAccountViewModel( sendEvent(ShareAccountEvent.Finish) } - is ShareAccountAction.OnDateChange -> { + is ShareAccountAction.OnSubmissionDateChange -> { mutableStateFlow.update { it.copy( submissionDate = action.date, @@ -142,10 +240,27 @@ class CreateShareAccountViewModel( } } - is ShareAccountAction.OnOpenDatePicker -> { + is ShareAccountAction.OnApplicationDateChange -> { mutableStateFlow.update { it.copy( - showDatePicker = action.state, + applicationDate = action.date, + applicationDateError = null, + ) + } + } + + is ShareAccountAction.OnOpenSubmissionDatePicker -> { + mutableStateFlow.update { + it.copy( + showSubmissionDatePicker = action.state, + ) + } + } + + is ShareAccountAction.OnOpenApplicationDatePicker -> { + mutableStateFlow.update { + it.copy( + showApplicationDatePicker = action.state, ) } } @@ -159,6 +274,33 @@ class CreateShareAccountViewModel( } } + is ShareAccountAction.OnSavingsAccountChange -> { + mutableStateFlow.update { + it.copy( + savingsAccountIdx = action.index, + savingsAccountError = null, + ) + } + } + + is ShareAccountAction.OnMinActiveFreqTypeChange -> { + mutableStateFlow.update { + it.copy( + minActivePeriodFreqTypeIdx = action.index, + minActivePeriodFreqTypeError = null, + ) + } + } + + is ShareAccountAction.OnLockInFreqTypeChange -> { + mutableStateFlow.update { + it.copy( + lockInPeriodFreqTypeIdx = action.index, + lockInPeriodFreqTypeError = null, + ) + } + } + is ShareAccountAction.OnExternalIdChange -> { mutableStateFlow.update { it.copy( @@ -167,6 +309,41 @@ class CreateShareAccountViewModel( } } + is ShareAccountAction.OnTotalSharesChange -> { + mutableStateFlow.update { + it.copy( + totalShares = action.value, + totalSharesError = null, + ) + } + } + + is ShareAccountAction.OnMinActiveFreqChange -> { + mutableStateFlow.update { + it.copy( + minActivePeriodFreq = action.value, + minActivePeriodFreqError = null, + ) + } + } + + is ShareAccountAction.OnLockInFreqChange -> { + mutableStateFlow.update { + it.copy( + lockInPeriodFreq = action.value, + lockInPeriodFreqError = null, + ) + } + } + + is ShareAccountAction.OnIsDividendAllowedClicked -> { + mutableStateFlow.update { + it.copy( + isDividendAllowed = !it.isDividendAllowed, + ) + } + } + ShareAccountAction.Retry -> { loadShareTemplate(route.clientId) } @@ -175,6 +352,10 @@ class CreateShareAccountViewModel( handleOnDetailNext() } + ShareAccountAction.OnTermsNext -> { + handleOnTermsNext() + } + ShareAccountAction.PreviousStep -> { moveToPreviousStep() } @@ -194,10 +375,35 @@ constructor( val submissionDate: String = DateHelper.getDateAsStringFromLong( Clock.System.now().toEpochMilliseconds(), ), - val showDatePicker: Boolean = false, + val showSubmissionDatePicker: Boolean = false, val productOption: List = emptyList(), + val savingsAccountOptions: List = emptyList(), + val lockInPeriodFrequencyTypeOptions: List = emptyList(), + val minimumActivePeriodFrequencyTypeOptions: List = emptyList(), + val currency: String? = null, + val currentPrice: String? = null, + val totalShares: String = "", + val totalSharesError: StringResource? = null, + val savingsAccountIdx: Int? = null, + val savingsAccountError: StringResource? = null, + val applicationDate: String = DateHelper.getDateAsStringFromLong( + Clock.System.now().toEpochMilliseconds(), + ), + val applicationDateError: StringResource? = null, + val showApplicationDatePicker: Boolean = false, + val isDividendAllowed: Boolean = false, + val minActivePeriodFreq: String = "", + val minActivePeriodFreqError: StringResource? = null, + val minActivePeriodFreqTypeIdx: Int? = null, + val minActivePeriodFreqTypeError: StringResource? = null, + val lockInPeriodFreq: String = "", + val lockInPeriodFreqError: StringResource? = null, + val lockInPeriodFreqTypeIdx: Int? = null, + val lockInPeriodFreqTypeError: StringResource? = null, val screenState: ScreenState = ScreenState.Loading, ) { + val selectedProduct: ProductOption? get() = shareProductIndex?.let { productOption.getOrNull(it) } + interface ScreenState { object Loading : ScreenState object Success : ScreenState @@ -212,10 +418,20 @@ sealed interface ShareAccountAction { object NavigateBack : ShareAccountAction object Finish : ShareAccountAction data class OnShareProductChange(val index: Int) : ShareAccountAction - data class OnDateChange(val date: String) : ShareAccountAction - data class OnOpenDatePicker(val state: Boolean) : ShareAccountAction + data class OnSavingsAccountChange(val index: Int) : ShareAccountAction + data class OnSubmissionDateChange(val date: String) : ShareAccountAction + data class OnApplicationDateChange(val date: String) : ShareAccountAction + data class OnOpenSubmissionDatePicker(val state: Boolean) : ShareAccountAction + data class OnOpenApplicationDatePicker(val state: Boolean) : ShareAccountAction data class OnExternalIdChange(val value: String?) : ShareAccountAction + data class OnTotalSharesChange(val value: String) : ShareAccountAction + data class OnMinActiveFreqChange(val value: String) : ShareAccountAction + data class OnMinActiveFreqTypeChange(val index: Int?) : ShareAccountAction + data class OnLockInFreqChange(val value: String) : ShareAccountAction + data class OnLockInFreqTypeChange(val index: Int?) : ShareAccountAction + data object OnIsDividendAllowedClicked : ShareAccountAction object OnDetailNext : ShareAccountAction + object OnTermsNext : ShareAccountAction object Retry : ShareAccountAction } diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/DetailsPage.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/DetailsPage.kt index 85278270417..4eecc67a8df 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/DetailsPage.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/DetailsPage.kt @@ -10,14 +10,14 @@ package com.mifos.feature.client.createShareAccount.pages import androidclient.feature.client.generated.resources.Res -import androidclient.feature.client.generated.resources.share_account_back -import androidclient.feature.client.generated.resources.share_account_detail_date_cancel -import androidclient.feature.client.generated.resources.share_account_detail_date_select -import androidclient.feature.client.generated.resources.share_account_detail_external_id -import androidclient.feature.client.generated.resources.share_account_detail_product_name -import androidclient.feature.client.generated.resources.share_account_detail_submission_date -import androidclient.feature.client.generated.resources.share_account_details -import androidclient.feature.client.generated.resources.share_account_next +import androidclient.feature.client.generated.resources.feature_share_account_back +import androidclient.feature.client.generated.resources.feature_share_account_detail_date_cancel +import androidclient.feature.client.generated.resources.feature_share_account_detail_date_select +import androidclient.feature.client.generated.resources.feature_share_account_detail_external_id +import androidclient.feature.client.generated.resources.feature_share_account_detail_product_name +import androidclient.feature.client.generated.resources.feature_share_account_detail_submission_date +import androidclient.feature.client.generated.resources.feature_share_account_details +import androidclient.feature.client.generated.resources.feature_share_account_next import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -58,36 +58,36 @@ fun DetailsPage( initialSelectedDateMillis = Clock.System.now().toEpochMilliseconds(), selectableDates = object : SelectableDates { override fun isSelectableDate(utcTimeMillis: Long): Boolean { - return utcTimeMillis >= Clock.System.now().toEpochMilliseconds().minus(86_400_000L) + return utcTimeMillis <= Clock.System.now().toEpochMilliseconds() } }, ) - if (state.showDatePicker) { + if (state.showSubmissionDatePicker) { DatePickerDialog( onDismissRequest = { - onAction(ShareAccountAction.OnOpenDatePicker(state = false)) + onAction(ShareAccountAction.OnOpenSubmissionDatePicker(state = false)) }, confirmButton = { TextButton( onClick = { - onAction(ShareAccountAction.OnOpenDatePicker(state = false)) + onAction(ShareAccountAction.OnOpenSubmissionDatePicker(state = false)) submissionDatePickerState.selectedDateMillis?.let { onAction( - ShareAccountAction.OnDateChange( + ShareAccountAction.OnSubmissionDateChange( DateHelper.getDateAsStringFromLong(it), ), ) } }, - ) { Text(stringResource(Res.string.share_account_detail_date_select)) } + ) { Text(stringResource(Res.string.feature_share_account_detail_date_select)) } }, dismissButton = { TextButton( onClick = { - onAction(ShareAccountAction.OnOpenDatePicker(state = false)) + onAction(ShareAccountAction.OnOpenSubmissionDatePicker(state = false)) }, - ) { Text(stringResource(Res.string.share_account_detail_date_cancel)) } + ) { Text(stringResource(Res.string.feature_share_account_detail_date_cancel)) } }, ) { DatePicker(state = submissionDatePickerState) @@ -99,7 +99,7 @@ fun DetailsPage( modifier = modifier.weight(1f).verticalScroll(rememberScrollState()), ) { Text( - text = stringResource(Res.string.share_account_details), + text = stringResource(Res.string.feature_share_account_details), style = MifosTypography.labelLargeEmphasized, ) Spacer(Modifier.height(DesignToken.padding.large)) @@ -117,29 +117,29 @@ fun DetailsPage( options = state.productOption.map { it.name }, - label = stringResource(Res.string.share_account_detail_product_name), + label = stringResource(Res.string.feature_share_account_detail_product_name), errorMessage = state.shareProductError?.let { stringResource(it) }, ) MifosDatePickerTextField( value = state.submissionDate, - label = stringResource(Res.string.share_account_detail_submission_date), + label = stringResource(Res.string.feature_share_account_detail_submission_date), openDatePicker = { - onAction(ShareAccountAction.OnOpenDatePicker(true)) + onAction(ShareAccountAction.OnOpenSubmissionDatePicker(true)) }, ) - + Spacer(Modifier.height(DesignToken.padding.large)) MifosOutlinedTextField( value = state.externalId ?: "", onValueChange = { onAction(ShareAccountAction.OnExternalIdChange(it)) }, - label = stringResource(Res.string.share_account_detail_external_id), + label = stringResource(Res.string.feature_share_account_detail_external_id), ) Spacer(Modifier.height(DesignToken.padding.large)) } MifosTwoButtonRow( - firstBtnText = stringResource(Res.string.share_account_back), - secondBtnText = stringResource(Res.string.share_account_next), + firstBtnText = stringResource(Res.string.feature_share_account_back), + secondBtnText = stringResource(Res.string.feature_share_account_next), onFirstBtnClick = { onAction(ShareAccountAction.NavigateBack) }, diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/TermsPage.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/TermsPage.kt index 4525cece064..d5bad2e53d5 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/TermsPage.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/createShareAccount/pages/TermsPage.kt @@ -9,23 +9,271 @@ */ package com.mifos.feature.client.createShareAccount.pages +import androidclient.feature.client.generated.resources.Res +import androidclient.feature.client.generated.resources.feature_share_account_back +import androidclient.feature.client.generated.resources.feature_share_account_detail_date_cancel +import androidclient.feature.client.generated.resources.feature_share_account_detail_date_select +import androidclient.feature.client.generated.resources.feature_share_account_next +import androidclient.feature.client.generated.resources.feature_share_account_terms +import androidclient.feature.client.generated.resources.feature_share_account_terms_allow_dividends +import androidclient.feature.client.generated.resources.feature_share_account_terms_application_date +import androidclient.feature.client.generated.resources.feature_share_account_terms_currency +import androidclient.feature.client.generated.resources.feature_share_account_terms_current_price +import androidclient.feature.client.generated.resources.feature_share_account_terms_default_savings_account +import androidclient.feature.client.generated.resources.feature_share_account_terms_frequency +import androidclient.feature.client.generated.resources.feature_share_account_terms_lock_in_period +import androidclient.feature.client.generated.resources.feature_share_account_terms_min_active_period +import androidclient.feature.client.generated.resources.feature_share_account_terms_total_shares +import androidclient.feature.client.generated.resources.feature_share_account_terms_type import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.material3.Button +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SelectableDates import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberDatePickerState import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp +import androidx.compose.ui.text.input.KeyboardType +import com.mifos.core.common.utils.DateHelper +import com.mifos.core.designsystem.component.MifosDatePickerTextField +import com.mifos.core.designsystem.component.MifosOutlinedTextField +import com.mifos.core.designsystem.component.MifosTextFieldConfig +import com.mifos.core.designsystem.component.MifosTextFieldDropdown +import com.mifos.core.designsystem.theme.DesignToken +import com.mifos.core.designsystem.theme.MifosTypography +import com.mifos.core.ui.components.MifosCheckBox +import com.mifos.core.ui.components.MifosTwoButtonRow +import com.mifos.feature.client.createShareAccount.ShareAccountAction +import com.mifos.feature.client.createShareAccount.ShareAccountState +import org.jetbrains.compose.resources.stringResource +import kotlin.time.Clock +import kotlin.time.ExperimentalTime +@OptIn(ExperimentalMaterial3Api::class, ExperimentalTime::class) @Composable -fun TermsPage(onNext: () -> Unit) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text("Terms Page") - Spacer(Modifier.height(8.dp)) - Button(onClick = onNext) { - Text("Next Button") +fun TermsPage( + state: ShareAccountState, + onAction: (ShareAccountAction) -> Unit, + modifier: Modifier = Modifier, +) { + val applicationDatePickerState = rememberDatePickerState( + initialSelectedDateMillis = Clock.System.now().toEpochMilliseconds(), + selectableDates = object : SelectableDates { + override fun isSelectableDate(utcTimeMillis: Long): Boolean { + return utcTimeMillis <= Clock.System.now().toEpochMilliseconds() + } + }, + ) + + if (state.showApplicationDatePicker) { + DatePickerDialog( + onDismissRequest = { + onAction(ShareAccountAction.OnOpenApplicationDatePicker(state = false)) + }, + confirmButton = { + TextButton( + onClick = { + onAction(ShareAccountAction.OnOpenApplicationDatePicker(state = false)) + applicationDatePickerState.selectedDateMillis?.let { + onAction( + ShareAccountAction.OnApplicationDateChange( + DateHelper.getDateAsStringFromLong(it), + ), + ) + } + }, + ) { Text(stringResource(Res.string.feature_share_account_detail_date_select)) } + }, + dismissButton = { + TextButton( + onClick = { + onAction(ShareAccountAction.OnOpenApplicationDatePicker(state = false)) + }, + ) { Text(stringResource(Res.string.feature_share_account_detail_date_cancel)) } + }, + ) { + DatePicker(state = applicationDatePickerState) + } + } + + Column(modifier = Modifier.fillMaxSize().padding(bottom = DesignToken.padding.large)) { + Column( + modifier = modifier.weight(1f).verticalScroll(rememberScrollState()), + ) { + Text( + text = stringResource(Res.string.feature_share_account_terms), + style = MifosTypography.labelLargeEmphasized, + ) + Spacer(Modifier.height(DesignToken.padding.large)) + + MifosTextFieldDropdown( + value = state.currency.orEmpty(), + onValueChanged = {}, + onOptionSelected = { _, _ -> }, + options = emptyList(), + label = stringResource(Res.string.feature_share_account_terms_currency), + enabled = false, + ) + MifosOutlinedTextField( + value = state.currentPrice.orEmpty(), + onValueChange = {}, + label = stringResource(Res.string.feature_share_account_terms_current_price), + config = MifosTextFieldConfig( + enabled = false, + ), + ) + Spacer(Modifier.height(DesignToken.padding.large)) + + MifosOutlinedTextField( + value = state.totalShares, + onValueChange = { + onAction(ShareAccountAction.OnTotalSharesChange(it)) + }, + label = stringResource(Res.string.feature_share_account_terms_total_shares), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + ), + isError = state.totalSharesError != null, + errorText = state.totalSharesError?.let { stringResource(it) }, + ), + ) + Spacer(Modifier.height(DesignToken.padding.large)) + + MifosTextFieldDropdown( + value = if (state.savingsAccountIdx == null) { + "" + } else { + state.savingsAccountOptions.getOrNull(state.savingsAccountIdx)?.accountNo.orEmpty() + }, + onValueChanged = {}, + onOptionSelected = { index, value -> + onAction(ShareAccountAction.OnSavingsAccountChange(index)) + }, + options = state.savingsAccountOptions.map { + it.accountNo + (it.savingsProductName?.let { name -> " - $name" }.orEmpty()) + }, + label = stringResource(Res.string.feature_share_account_terms_default_savings_account), + errorMessage = state.savingsAccountError?.let { stringResource(it) }, + ) + + MifosDatePickerTextField( + value = state.applicationDate, + label = stringResource(Res.string.feature_share_account_terms_application_date), + openDatePicker = { + onAction(ShareAccountAction.OnOpenApplicationDatePicker(true)) + }, + errorMessage = state.applicationDateError?.let { stringResource(it) }, + ) + + MifosCheckBox( + text = stringResource(Res.string.feature_share_account_terms_allow_dividends), + checked = state.isDividendAllowed, + onCheckChanged = { + onAction(ShareAccountAction.OnIsDividendAllowedClicked) + }, + ) + Spacer(Modifier.height(DesignToken.padding.large)) + + Text( + text = stringResource(Res.string.feature_share_account_terms_min_active_period), + style = MifosTypography.labelLargeEmphasized, + ) + Spacer(Modifier.height(DesignToken.padding.large)) + + MifosOutlinedTextField( + value = state.minActivePeriodFreq, + onValueChange = { + onAction(ShareAccountAction.OnMinActiveFreqChange(it)) + }, + label = stringResource(Res.string.feature_share_account_terms_frequency), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + ), + isError = state.minActivePeriodFreqError != null, + errorText = state.minActivePeriodFreqError?.let { stringResource(it) }, + ), + ) + Spacer(Modifier.height(DesignToken.padding.large)) + + MifosTextFieldDropdown( + value = if (state.minActivePeriodFreqTypeIdx == null) { + "" + } else { + state.minimumActivePeriodFrequencyTypeOptions.getOrNull(state.minActivePeriodFreqTypeIdx)?.value.orEmpty() + }, + onValueChanged = {}, + onOptionSelected = { index, value -> + onAction(ShareAccountAction.OnMinActiveFreqTypeChange(index)) + }, + options = state.minimumActivePeriodFrequencyTypeOptions.map { + it.value + }, + enabled = state.minActivePeriodFreq.isNotBlank(), + label = stringResource(Res.string.feature_share_account_terms_type), + errorMessage = state.minActivePeriodFreqTypeError?.let { stringResource(it) }, + ) + + Text( + text = stringResource(Res.string.feature_share_account_terms_lock_in_period), + style = MifosTypography.labelLargeEmphasized, + ) + Spacer(Modifier.height(DesignToken.padding.large)) + + MifosOutlinedTextField( + value = state.lockInPeriodFreq, + onValueChange = { + onAction(ShareAccountAction.OnLockInFreqChange(it)) + }, + label = stringResource(Res.string.feature_share_account_terms_frequency), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + ), + isError = state.lockInPeriodFreqError != null, + errorText = state.lockInPeriodFreqError?.let { stringResource(it) }, + ), + ) + Spacer(Modifier.height(DesignToken.padding.large)) + + MifosTextFieldDropdown( + value = if (state.lockInPeriodFreqTypeIdx == null) { + "" + } else { + state.lockInPeriodFrequencyTypeOptions.getOrNull(state.lockInPeriodFreqTypeIdx)?.value.orEmpty() + }, + onValueChanged = {}, + onOptionSelected = { index, value -> + onAction(ShareAccountAction.OnLockInFreqTypeChange(index)) + }, + options = state.lockInPeriodFrequencyTypeOptions.map { + it.value + }, + enabled = state.lockInPeriodFreq.isNotBlank(), + label = stringResource(Res.string.feature_share_account_terms_type), + errorMessage = state.lockInPeriodFreqTypeError?.let { stringResource(it) }, + ) } + MifosTwoButtonRow( + firstBtnText = stringResource(Res.string.feature_share_account_back), + secondBtnText = stringResource(Res.string.feature_share_account_next), + onFirstBtnClick = { + onAction(ShareAccountAction.PreviousStep) + }, + onSecondBtnClick = { + onAction(ShareAccountAction.OnTermsNext) + }, + modifier = Modifier.padding(top = DesignToken.padding.small), + ) } }