diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/InterestPage.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/InterestPage.kt index 29f9a7f139..37a8a3535d 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/InterestPage.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/newFixedDepositAccount/pages/InterestPage.kt @@ -9,29 +9,889 @@ */ package com.mifos.feature.client.newFixedDepositAccount.pages -import androidclient.feature.client.generated.resources.Res -import androidclient.feature.client.generated.resources.step_interest +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +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.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +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.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import org.jetbrains.compose.resources.stringResource +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties + +// Data class for Rate Chart Item +data class RateChartItem( + val amountRange: String, + val description: String, + val period: String, + val interestRate: String, +) + +// Data class for Chart Information +data class ChartInformation( + val name: String, + val validFromDate: String, + val endDate: String, + val description: String, + val groupingByAmount: Boolean, +) @Composable fun InterestPage(onNext: () -> Unit) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { + var showRateChartModal by remember { mutableStateOf(false) } + var showActionModal by remember { mutableStateOf(false) } + var showEditDialog by remember { mutableStateOf(false) } + var showDeleteConfirmation by remember { mutableStateOf(false) } + var selectedItemIndex by remember { mutableStateOf(-1) } + + // Sample data - in real implementation, this would come from ViewModel/Repository + val chartInfo = remember { + ChartInformation( + name = "Yearly", + validFromDate = "01 January 2025", + endDate = "09 June 2025", + description = "2025 year interest rate", + groupingByAmount = false, + ) + } + + val rateChartItems = remember { + mutableStateListOf( + RateChartItem( + amountRange = "$200 - $250", + description = "First Period", + period = "1 Years", + interestRate = "7%", + ), + ) + } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 12.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Spacer(modifier = Modifier.height(8.dp)) + + // CARD 1: Interest Details + InterestDetailsCard( + chartInfo = chartInfo, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // CARD 2: View Interest Rate Chart (Inline Preview - NO Modal on main page) + ViewInterestRateChartCard( + rateChartItems = rateChartItems, + onViewClick = { showRateChartModal = true }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Navigation Buttons + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + OutlinedButton( + onClick = { /* Handle back - will be managed by stepper */ }, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(8.dp), + ) { + Text("Back") + } + + Button( + onClick = onNext, + modifier = Modifier.weight(1f), + enabled = rateChartItems.isNotEmpty(), + shape = RoundedCornerShape(8.dp), + ) { + Text("Next") + } + } + + Spacer(modifier = Modifier.height(8.dp)) + } + + // Rate Chart Modal (Full View - Opens on "View" button click) + if (showRateChartModal) { + RateChartModal( + rateChartItems = rateChartItems, + onDismiss = { showRateChartModal = false }, + onItemClick = { index -> + selectedItemIndex = index + showRateChartModal = false + showActionModal = true + }, + onDownload = { + // Handle download functionality + }, + ) + } + + // Action Modal (Edit/Delete options) + if (showActionModal && selectedItemIndex >= 0) { + ActionModal( + item = rateChartItems[selectedItemIndex], + onDismiss = { + showActionModal = false + showRateChartModal = true + }, + onEditClick = { + showActionModal = false + showEditDialog = true + }, + onDeleteClick = { + showActionModal = false + showDeleteConfirmation = true + }, + onDownload = { + // Handle download for this specific item + }, + ) + } + + // Edit Dialog + if (showEditDialog && selectedItemIndex >= 0) { + EditRateChartDialog( + item = rateChartItems[selectedItemIndex], + onDismiss = { + showEditDialog = false + showRateChartModal = true + }, + onConfirm = { updatedItem -> + rateChartItems[selectedItemIndex] = updatedItem + showEditDialog = false + showRateChartModal = true + }, + ) + } + + // Delete Confirmation Dialog + if (showDeleteConfirmation && selectedItemIndex >= 0) { + DeleteConfirmationDialog( + onDismiss = { + showDeleteConfirmation = false + showRateChartModal = true + }, + onConfirm = { + rateChartItems.removeAt(selectedItemIndex) + showDeleteConfirmation = false + showRateChartModal = true + }, + ) + } +} + +// CARD 1: Interest Details Card +@Composable +private fun InterestDetailsCard( + chartInfo: ChartInformation, + modifier: Modifier = Modifier, +) { + Card( + modifier = modifier, + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, Color(0xFFE0E0E0)), + colors = CardDefaults.cardColors( + containerColor = Color.White, + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + text = "Interest Details", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + color = Color(0xFF212121), + ) + + InfoRow(label = "Name:", value = chartInfo.name) + InfoRow(label = "Valid from Date:", value = chartInfo.validFromDate) + InfoRow(label = "End Date:", value = chartInfo.endDate) + InfoRow(label = "Description:", value = chartInfo.description) + InfoRow( + label = "Grouping by Amount:", + value = if (chartInfo.groupingByAmount) "Yes" else "No", + ) + } + } +} + +// CARD 2: View Interest Rate Chart - Header with bordered View button +@Composable +private fun ViewInterestRateChartCard( + rateChartItems: List, + onViewClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Card( + modifier = modifier, + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, Color(0xFFE0E0E0)), + colors = CardDefaults.cardColors( + containerColor = Color.White, + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "INTEREST RATE CHART", + style = MaterialTheme.typography.labelLarge, + fontWeight = FontWeight.SemiBold, + fontSize = 13.sp, + color = Color(0xFF212121), + ) + + // View button with border (like Figma) + OutlinedButton( + onClick = onViewClick, + shape = RoundedCornerShape(4.dp), + border = BorderStroke(1.dp, Color(0xFFE0E0E0)), + modifier = Modifier.height(32.dp), + ) { + Text( + text = "View", + color = MaterialTheme.colorScheme.primary, + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + ) + } + } + } +} + +// Pre-built component: Info Row +@Composable +private fun InfoRow(label: String, value: String) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top, + ) { Text( - stringResource(Res.string.step_interest), + text = label, + style = MaterialTheme.typography.bodyMedium, + fontSize = 14.sp, + color = Color(0xFF757575), + modifier = Modifier.weight(1f), + textAlign = TextAlign.Start, + ) + Text( + text = value, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + color = Color(0xFF212121), + modifier = Modifier.weight(1f), + textAlign = TextAlign.End, ) - Spacer(Modifier.height(8.dp)) - Button(onClick = onNext) { - Text("Next Button") + } +} + +// Pre-built component: Rate Chart Header Card (for modal) +@Composable +private fun RateChartHeaderCard() { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(0.dp, Color.Transparent), + colors = CardDefaults.cardColors( + containerColor = Color(0xFFF5F5F5), + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top, + ) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(3.dp), + ) { + Text( + text = "Amount Range", + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + color = Color(0xFF212121), + textAlign = TextAlign.Start, + ) + + Text( + text = "Description", + style = MaterialTheme.typography.bodySmall, + fontSize = 12.sp, + color = Color(0xFF757575), + textAlign = TextAlign.Start, + ) + + Text( + text = "Period", + style = MaterialTheme.typography.bodySmall, + fontSize = 12.sp, + color = Color(0xFF757575), + textAlign = TextAlign.Start, + ) + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = "@Interest Rate", + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + color = Color(0xFF4CAF50), + textAlign = TextAlign.End, + ) + + Text( + text = "▲", + fontSize = 10.sp, + color = Color(0xFF4CAF50), + ) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun RateChartModal( + rateChartItems: List, + onDismiss: () -> Unit, + onItemClick: (Int) -> Unit, + onDownload: () -> Unit, +) { + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false), + ) { + Surface( + modifier = Modifier + .fillMaxWidth(0.92f) // Standardized width for all modals + .padding(horizontal = 16.dp), // Consistent padding + shape = RoundedCornerShape(20.dp), + color = Color.White, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + ) { + // Header + Text( + text = "Rate Chart", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + fontSize = 22.sp, + color = Color(0xFF212121), + modifier = Modifier.padding(bottom = 16.dp), + ) + + // Amount Range Header Card + RateChartHeaderCard() + + Spacer(modifier = Modifier.height(12.dp)) + + // Rate Chart Items List + Column( + modifier = Modifier.weight(1f, fill = false), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + rateChartItems.forEachIndexed { index, item -> + RateChartItemCard( + item = item, + onClick = { onItemClick(index) }, + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // Action Buttons + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + OutlinedButton( + onClick = onDismiss, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, Color(0xFFBDBDBD)), + ) { + Text( + "Back", + color = Color(0xFF212121), + fontSize = 15.sp, + fontWeight = FontWeight.Medium, + ) + } + + Button( + onClick = onDownload, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF5C6BC0), + ), + ) { + Text( + "Download", + fontSize = 15.sp, + fontWeight = FontWeight.Medium, + ) + } + } + } + } + } +} + +// Pre-built component: Rate Chart Item Card +@Composable +private fun RateChartItemCard( + item: RateChartItem, + onClick: () -> Unit, +) { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, Color(0xFFE0E0E0)), + colors = CardDefaults.cardColors( + containerColor = Color.White, + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top, + ) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = item.amountRange, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + color = Color(0xFF212121), + textAlign = TextAlign.Start, + ) + + Text( + text = item.description, + style = MaterialTheme.typography.bodySmall, + fontSize = 13.sp, + color = Color(0xFF757575), + textAlign = TextAlign.Start, + ) + + Text( + text = item.period, + style = MaterialTheme.typography.bodySmall, + fontSize = 13.sp, + color = Color(0xFF757575), + textAlign = TextAlign.Start, + ) + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + text = item.interestRate, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + fontSize = 17.sp, + color = Color(0xFF4CAF50), + textAlign = TextAlign.End, + ) + + Text( + text = "›", + fontSize = 24.sp, + color = Color(0xFF9E9E9E), + modifier = Modifier.padding(start = 2.dp), + ) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ActionModal( + item: RateChartItem, + onDismiss: () -> Unit, + onEditClick: () -> Unit, + onDeleteClick: () -> Unit, + onDownload: () -> Unit, +) { + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false), + ) { + Surface( + modifier = Modifier + .fillMaxWidth(0.92f) // Same standardized width as RateChartModal + .padding(horizontal = 16.dp), // Consistent padding + shape = RoundedCornerShape(20.dp), + color = Color.White, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + verticalArrangement = Arrangement.spacedBy(14.dp), + ) { + // Header + Text( + text = "Rate Chart", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + fontSize = 22.sp, + color = Color(0xFF212121), + ) + + // Amount Range Header Card + RateChartHeaderCard() + + // Item Details Card + RateChartItemCard( + item = item, + onClick = { }, + ) + + // Action Options + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + // Edit Option + ActionOptionCard( + icon = Icons.Default.Edit, + label = "Edit", + onClick = onEditClick + ) + + // Delete Option + ActionOptionCard( + icon = Icons.Default.Delete, + label = "Delete", + onClick = onDeleteClick + ) + } + + Spacer(modifier = Modifier.height(4.dp)) + + // Bottom Buttons + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + OutlinedButton( + onClick = onDismiss, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, Color(0xFFBDBDBD)), + ) { + Text( + "Back", + color = Color(0xFF212121), + fontSize = 15.sp, + fontWeight = FontWeight.Medium, + ) + } + + Button( + onClick = onDownload, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF5C6BC0), + ), + ) { + Text( + "Download", + fontSize = 15.sp, + fontWeight = FontWeight.Medium, + ) + } + } + } + } + } +} + +// Pre-built component: Action Option Card +@Composable +private fun ActionOptionCard( + icon: androidx.compose.ui.graphics.vector.ImageVector, + label: String, + onClick: () -> Unit, +) { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(0.dp, Color.Transparent), + colors = CardDefaults.cardColors( + containerColor = Color(0xFFFAFAFA), + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start, + ) { + Icon( + imageVector = icon, + contentDescription = label, + tint = Color(0xFF757575), + modifier = Modifier.padding(end = 12.dp), + ) + + Text( + text = label, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Normal, + fontSize = 15.sp, + color = Color(0xFF212121), + textAlign = TextAlign.Start, + ) } } } + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun EditRateChartDialog( + item: RateChartItem, + onDismiss: () -> Unit, + onConfirm: (RateChartItem) -> Unit, +) { + var amountRange by remember { mutableStateOf(item.amountRange) } + var description by remember { mutableStateOf(item.description) } + var period by remember { mutableStateOf(item.period) } + var interestRate by remember { mutableStateOf(item.interestRate.replace("%", "")) } + var interestRateError by remember { mutableStateOf(false) } + + AlertDialog( + onDismissRequest = onDismiss, + shape = RoundedCornerShape(16.dp), + containerColor = Color.White, + title = { + Text( + text = "Edit Interest Rate", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + color = Color(0xFF212121), + ) + }, + text = { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(14.dp), + ) { + OutlinedTextField( + value = amountRange, + onValueChange = { amountRange = it }, + label = { Text("Amount Range") }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + ) + + OutlinedTextField( + value = description, + onValueChange = { description = it }, + label = { Text("Description") }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + ) + + OutlinedTextField( + value = period, + onValueChange = { period = it }, + label = { Text("Period") }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + ) + + OutlinedTextField( + value = interestRate, + onValueChange = { + interestRate = it + val rate = it.toDoubleOrNull() + interestRateError = rate == null || rate <= 0 || rate >= 100 + }, + label = { Text("Interest Rate (%)") }, + isError = interestRateError, + supportingText = { + if (interestRateError) { + Text( + "Please enter a valid percentage (0-100)", + color = Color(0xFFD32F2F), + ) + } + }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + ) + } + }, + confirmButton = { + Button( + onClick = { + val rate = interestRate.toDoubleOrNull() + if (rate != null && rate > 0 && rate < 100) { + onConfirm( + RateChartItem( + amountRange = amountRange, + description = description, + period = period, + interestRate = "$interestRate%", + ), + ) + } + }, + enabled = !interestRateError && interestRate.isNotEmpty(), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF5C6BC0), + ), + ) { + Text("Save", fontWeight = FontWeight.Medium) + } + }, + dismissButton = { + TextButton( + onClick = onDismiss, + shape = RoundedCornerShape(8.dp), + ) { + Text("Cancel", color = Color(0xFF757575)) + } + }, + ) +} + +@Composable +private fun DeleteConfirmationDialog( + onDismiss: () -> Unit, + onConfirm: () -> Unit, +) { + AlertDialog( + onDismissRequest = onDismiss, + shape = RoundedCornerShape(16.dp), + containerColor = Color.White, + title = { + Text( + text = "Delete Rate Chart Entry", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + color = Color(0xFF212121), + ) + }, + text = { + Text( + "Are you sure you want to delete this interest rate entry? This action cannot be undone.", + color = Color(0xFF757575), + fontSize = 14.sp, + ) + }, + confirmButton = { + Button( + onClick = onConfirm, + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFFE57373), + ), + ) { + Text("Delete", fontWeight = FontWeight.Medium) + } + }, + dismissButton = { + TextButton( + onClick = onDismiss, + shape = RoundedCornerShape(8.dp), + ) { + Text("Cancel", color = Color(0xFF757575)) + } + }, + ) +}