diff --git a/.idea/misc.xml b/.idea/misc.xml index 74dd639..991a888 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + diff --git a/README.md b/README.md index 64f32fe..9286bcd 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ Ensure your MariaDB database has the following tables: -- Table for SAIs CREATE TABLE SAIs ( id_sai INT AUTO_INCREMENT PRIMARY KEY, - ns VARCHAR(255) NOT NULL + ns VARCHAR(255) NOT NULL UNIQUE, + modelo VARCHAR(255) ); -- Table for Batteries diff --git a/app/src/main/java/com/example/saipp/data/SaiRepository.kt b/app/src/main/java/com/example/saipp/data/SaiRepository.kt index 0533e5f..8cd7b4f 100644 --- a/app/src/main/java/com/example/saipp/data/SaiRepository.kt +++ b/app/src/main/java/com/example/saipp/data/SaiRepository.kt @@ -1,25 +1,24 @@ package com.example.saipp.data -import android.util.Log import com.example.saipp.data.model.Sai import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.sql.SQLException class SaiRepository { - companion object { - private const val TAG = "SaiRepository" - } - suspend fun getAll(): Result> = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) try { - val sql = "SELECT id_sai, ns FROM SAIs" + val sql = "SELECT id_sai, ns, modelo FROM SAIs" val statement = connection.prepareStatement(sql) val resultSet = statement.executeQuery() val sais = mutableListOf() while (resultSet.next()) { - sais.add(Sai(id = resultSet.getInt("id_sai"), ns = resultSet.getString("ns"))) + sais.add(Sai( + id = resultSet.getInt("id_sai"), + ns = resultSet.getString("ns"), + modelo = resultSet.getString("modelo") + )) } Result.success(sais) } catch (e: SQLException) { @@ -29,12 +28,32 @@ class SaiRepository { } } - suspend fun insert(ns: String): Result = withContext(Dispatchers.IO) { + suspend fun isNsRegistered(ns: String): Result = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) try { - val sql = "INSERT INTO SAIs (ns) VALUES (?)" + val sql = "SELECT COUNT(*) FROM SAIs WHERE ns = ?" val statement = connection.prepareStatement(sql) statement.setString(1, ns) + val resultSet = statement.executeQuery() + if (resultSet.next()) { + Result.success(resultSet.getInt(1) > 0) + } else { + Result.success(false) + } + } catch (e: SQLException) { + Result.failure(e) + } finally { + connection.close() + } + } + + suspend fun insert(ns: String, modelo: String?): Result = withContext(Dispatchers.IO) { + val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) + try { + val sql = "INSERT INTO SAIs (ns, modelo) VALUES (?, ?)" + val statement = connection.prepareStatement(sql) + statement.setString(1, ns) + statement.setString(2, modelo) if (statement.executeUpdate() > 0) Result.success(Unit) else Result.failure(Exception("no_rows_inserted")) } catch (e: SQLException) { @@ -47,10 +66,11 @@ class SaiRepository { suspend fun update(sai: Sai): Result = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) try { - val sql = "UPDATE SAIs SET ns = ? WHERE id_sai = ?" + val sql = "UPDATE SAIs SET ns = ?, modelo = ? WHERE id_sai = ?" val statement = connection.prepareStatement(sql) statement.setString(1, sai.ns) - statement.setInt(2, sai.id) + statement.setString(2, sai.modelo) + statement.setInt(3, sai.id) if (statement.executeUpdate() > 0) Result.success(Unit) else Result.failure(Exception("update_failed")) } catch (e: SQLException) { @@ -78,12 +98,16 @@ class SaiRepository { suspend fun getById(id: Int): Result = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) try { - val sql = "SELECT id_sai, ns FROM SAIs WHERE id_sai = ?" + val sql = "SELECT id_sai, ns, modelo FROM SAIs WHERE id_sai = ?" val statement = connection.prepareStatement(sql) statement.setInt(1, id) val resultSet = statement.executeQuery() if (resultSet.next()) { - Result.success(Sai(id = resultSet.getInt("id_sai"), ns = resultSet.getString("ns"))) + Result.success(Sai( + id = resultSet.getInt("id_sai"), + ns = resultSet.getString("ns"), + modelo = resultSet.getString("modelo") + )) } else { Result.failure(Exception("not_found")) } diff --git a/app/src/main/java/com/example/saipp/data/model/Sai.kt b/app/src/main/java/com/example/saipp/data/model/Sai.kt index bdcf3fc..786c977 100644 --- a/app/src/main/java/com/example/saipp/data/model/Sai.kt +++ b/app/src/main/java/com/example/saipp/data/model/Sai.kt @@ -2,5 +2,6 @@ package com.example.saipp.data.model data class Sai( val id: Int = 0, - val ns: String + val ns: String, + val modelo: String? = null ) diff --git a/app/src/main/java/com/example/saipp/ui/Components.kt b/app/src/main/java/com/example/saipp/ui/Components.kt index c8f190c..f503717 100644 --- a/app/src/main/java/com/example/saipp/ui/Components.kt +++ b/app/src/main/java/com/example/saipp/ui/Components.kt @@ -2,19 +2,80 @@ package com.example.saipp.ui import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.QrCodeScanner +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import com.example.saipp.R +import com.example.saipp.ui.scanner.BarcodeScannerView + +@Composable +fun SearchBar( + query: String, + onQueryChange: (String) -> Unit, + modifier: Modifier = Modifier, + onScanClick: (() -> Unit)? = null +) { + OutlinedTextField( + value = query, + onValueChange = onQueryChange, + modifier = modifier.fillMaxWidth(), + placeholder = { Text(stringResource(R.string.placeholder_search)) }, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, + trailingIcon = { + if (onScanClick != null) { + IconButton(onClick = onScanClick) { + Icon( + imageVector = Icons.Default.QrCodeScanner, + contentDescription = stringResource(R.string.scan_to_search) + ) + } + } + }, + singleLine = true, + shape = MaterialTheme.shapes.medium + ) +} + +@Composable +fun ScannerDialog( + onDismiss: () -> Unit, + onCodeScanned: (String) -> Unit +) { + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + Box(modifier = Modifier.fillMaxSize()) { + BarcodeScannerView(onBarcodeDetected = { code -> + onCodeScanned(code) + onDismiss() + }) + + TextButton( + onClick = onDismiss, + modifier = Modifier + .align(Alignment.TopStart) + .padding(16.dp) + ) { + Text(stringResource(R.string.button_close), color = Color.White) + } + } + } + } +} @Composable fun ResultDialog( diff --git a/app/src/main/java/com/example/saipp/ui/management/AddAssignmentScreen.kt b/app/src/main/java/com/example/saipp/ui/management/AddAssignmentScreen.kt index aa18886..19b45da 100644 --- a/app/src/main/java/com/example/saipp/ui/management/AddAssignmentScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/AddAssignmentScreen.kt @@ -1,9 +1,12 @@ package com.example.saipp.ui.management import android.widget.Toast +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.CalendarMonth +import androidx.compose.material.icons.filled.QrCodeScanner import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -17,7 +20,10 @@ import com.example.saipp.data.SaiRepository import com.example.saipp.data.model.Assignment import com.example.saipp.data.model.Person import com.example.saipp.data.model.Sai +import com.example.saipp.ui.ScannerDialog import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.* @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -32,20 +38,83 @@ fun AddAssignmentScreen( var selectedSaiId by remember { mutableStateOf(null) } var selectedPersonId by remember { mutableStateOf(null) } - var assignedDate by remember { mutableStateOf("") } - var returnDate by remember { mutableStateOf("") } + + var assignedDateDisplay by remember { mutableStateOf("") } + var assignedDateDb by remember { mutableStateOf("") } + var returnDateDisplay by remember { mutableStateOf("") } + var returnDateDb by remember { mutableStateOf("") } var sais by remember { mutableStateOf>(emptyList()) } var people by remember { mutableStateOf>(emptyList()) } var isSaving by remember { mutableStateOf(false) } var saiExpanded by remember { mutableStateOf(false) } var personExpanded by remember { mutableStateOf(false) } + + var showAssignedDatePicker by remember { mutableStateOf(false) } + var showReturnDatePicker by remember { mutableStateOf(false) } + var showQuickScanner by remember { mutableStateOf(false) } + + val assignedDatePickerState = rememberDatePickerState() + val returnDatePickerState = rememberDatePickerState() LaunchedEffect(Unit) { saiRepository.getAll().onSuccess { sais = it } personRepository.getAll().onSuccess { people = it } } + if (showAssignedDatePicker) { + DatePickerDialog( + onDismissRequest = { showAssignedDatePicker = false }, + confirmButton = { + TextButton(onClick = { + assignedDatePickerState.selectedDateMillis?.let { millis -> + val date = Date(millis) + assignedDateDisplay = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(date) + assignedDateDb = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(date) + } + showAssignedDatePicker = false + }) { Text("Aceptar") } + }, + dismissButton = { + TextButton(onClick = { showAssignedDatePicker = false }) { Text("Cancelar") } + } + ) { DatePicker(state = assignedDatePickerState) } + } + + if (showReturnDatePicker) { + DatePickerDialog( + onDismissRequest = { showReturnDatePicker = false }, + confirmButton = { + TextButton(onClick = { + returnDatePickerState.selectedDateMillis?.let { millis -> + val date = Date(millis) + returnDateDisplay = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(date) + returnDateDb = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(date) + } + showReturnDatePicker = false + }) { Text("Aceptar") } + }, + dismissButton = { + TextButton(onClick = { showReturnDatePicker = false }) { Text("Cancelar") } + } + ) { DatePicker(state = returnDatePickerState) } + } + + if (showQuickScanner) { + ScannerDialog( + onDismiss = { showQuickScanner = false }, + onCodeScanned = { code -> + val matchingSai = sais.find { it.ns.equals(code, ignoreCase = true) } + if (matchingSai != null) { + selectedSaiId = matchingSai.id + } else { + Toast.makeText(context, "SAI no encontrado: $code", Toast.LENGTH_SHORT).show() + } + showQuickScanner = false + } + ) + } + Scaffold( topBar = { TopAppBar( @@ -75,6 +144,14 @@ fun AddAssignmentScreen( onValueChange = {}, readOnly = true, label = { Text(stringResource(R.string.label_sai)) }, + leadingIcon = { + IconButton(onClick = { showQuickScanner = true }) { + Icon( + imageVector = Icons.Default.QrCodeScanner, + contentDescription = "Escanear SAI" + ) + } + }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = saiExpanded) }, modifier = Modifier.menuAnchor().fillMaxWidth() ) @@ -129,19 +206,31 @@ fun AddAssignmentScreen( Spacer(modifier = Modifier.height(16.dp)) OutlinedTextField( - value = assignedDate, - onValueChange = { assignedDate = it }, + value = assignedDateDisplay, + onValueChange = {}, + readOnly = true, label = { Text(stringResource(R.string.label_assigned_date)) }, - modifier = Modifier.fillMaxWidth() + trailingIcon = { + IconButton(onClick = { showAssignedDatePicker = true }) { + Icon(Icons.Default.CalendarMonth, contentDescription = "Seleccionar fecha") + } + }, + modifier = Modifier.fillMaxWidth().clickable { showAssignedDatePicker = true } ) Spacer(modifier = Modifier.height(16.dp)) OutlinedTextField( - value = returnDate, - onValueChange = { returnDate = it }, + value = returnDateDisplay, + onValueChange = {}, + readOnly = true, label = { Text(stringResource(R.string.label_return_date)) }, - modifier = Modifier.fillMaxWidth() + trailingIcon = { + IconButton(onClick = { showReturnDatePicker = true }) { + Icon(Icons.Default.CalendarMonth, contentDescription = "Seleccionar fecha") + } + }, + modifier = Modifier.fillMaxWidth().clickable { showReturnDatePicker = true } ) Spacer(modifier = Modifier.height(32.dp)) @@ -155,8 +244,8 @@ fun AddAssignmentScreen( Assignment( idSai = selectedSaiId!!, idPersona = selectedPersonId!!, - assignedDate = assignedDate.takeIf { it.isNotBlank() }, - returnDate = returnDate.takeIf { it.isNotBlank() } + assignedDate = assignedDateDb.takeIf { it.isNotBlank() }, + returnDate = returnDateDb.takeIf { it.isNotBlank() } ) ).onSuccess { Toast.makeText(context, "Asignación guardada", Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/example/saipp/ui/management/AddBatteryScreen.kt b/app/src/main/java/com/example/saipp/ui/management/AddBatteryScreen.kt index 59fd26e..27528a7 100644 --- a/app/src/main/java/com/example/saipp/ui/management/AddBatteryScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/AddBatteryScreen.kt @@ -1,9 +1,12 @@ package com.example.saipp.ui.management import android.widget.Toast +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.CalendarMonth +import androidx.compose.material.icons.filled.QrCodeScanner import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -15,7 +18,10 @@ import com.example.saipp.data.BatteryRepository import com.example.saipp.data.SaiRepository import com.example.saipp.data.model.Battery import com.example.saipp.data.model.Sai +import com.example.saipp.ui.ScannerDialog import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.* @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -28,17 +34,64 @@ fun AddBatteryScreen( val saiRepository = remember { SaiRepository() } var brand by remember { mutableStateOf("") } - var installDate by remember { mutableStateOf("") } + var installDateDisplay by remember { mutableStateOf("") } // DD-MM-AAAA + var installDateDb by remember { mutableStateOf("") } // YYYY-MM-DD var selectedSaiId by remember { mutableStateOf(null) } var sais by remember { mutableStateOf>(emptyList()) } var isSaving by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) } + var showDatePicker by remember { mutableStateOf(false) } + var showQuickScanner by remember { mutableStateOf(false) } + + val datePickerState = rememberDatePickerState() LaunchedEffect(Unit) { saiRepository.getAll().onSuccess { sais = it } } + if (showDatePicker) { + DatePickerDialog( + onDismissRequest = { showDatePicker = false }, + confirmButton = { + TextButton(onClick = { + datePickerState.selectedDateMillis?.let { millis -> + val date = Date(millis) + val displayFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + val dbFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + installDateDisplay = displayFormat.format(date) + installDateDb = dbFormat.format(date) + } + showDatePicker = false + }) { + Text("Aceptar") + } + }, + dismissButton = { + TextButton(onClick = { showDatePicker = false }) { + Text("Cancelar") + } + } + ) { + DatePicker(state = datePickerState) + } + } + + if (showQuickScanner) { + ScannerDialog( + onDismiss = { showQuickScanner = false }, + onCodeScanned = { code -> + val matchingSai = sais.find { it.ns.equals(code, ignoreCase = true) } + if (matchingSai != null) { + selectedSaiId = matchingSai.id + } else { + Toast.makeText(context, "SAI no encontrado: $code", Toast.LENGTH_SHORT).show() + } + showQuickScanner = false + } + ) + } + Scaffold( topBar = { TopAppBar( @@ -68,6 +121,14 @@ fun AddBatteryScreen( onValueChange = {}, readOnly = true, label = { Text(stringResource(R.string.label_sai)) }, + leadingIcon = { + IconButton(onClick = { showQuickScanner = true }) { + Icon( + imageVector = Icons.Default.QrCodeScanner, + contentDescription = "Escanear SAI" + ) + } + }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, modifier = Modifier.menuAnchor().fillMaxWidth() ) @@ -106,10 +167,18 @@ fun AddBatteryScreen( Spacer(modifier = Modifier.height(16.dp)) OutlinedTextField( - value = installDate, - onValueChange = { installDate = it }, + value = installDateDisplay, + onValueChange = {}, + readOnly = true, label = { Text(stringResource(R.string.label_install_date)) }, - modifier = Modifier.fillMaxWidth() + trailingIcon = { + IconButton(onClick = { showDatePicker = true }) { + Icon(Icons.Default.CalendarMonth, contentDescription = "Seleccionar fecha") + } + }, + modifier = Modifier + .fillMaxWidth() + .clickable { showDatePicker = true } ) Spacer(modifier = Modifier.height(32.dp)) @@ -122,7 +191,7 @@ fun AddBatteryScreen( Battery( idSai = selectedSaiId, brand = brand, - installDate = installDate.takeIf { it.isNotBlank() } + installDate = installDateDb.takeIf { it.isNotBlank() } ) ).onSuccess { Toast.makeText(context, "Batería añadida", Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/example/saipp/ui/management/AssignmentListScreen.kt b/app/src/main/java/com/example/saipp/ui/management/AssignmentListScreen.kt index d6b7f3a..c93dc93 100644 --- a/app/src/main/java/com/example/saipp/ui/management/AssignmentListScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/AssignmentListScreen.kt @@ -17,7 +17,11 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.example.saipp.data.AssignmentRepository import com.example.saipp.data.model.Assignment +import com.example.saipp.ui.ScannerDialog +import com.example.saipp.ui.SearchBar import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.* @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -31,6 +35,8 @@ fun AssignmentListScreen( val repository = remember { AssignmentRepository() } var items by remember { mutableStateOf>(emptyList()) } var isLoading by remember { mutableStateOf(true) } + var searchQuery by remember { mutableStateOf("") } + var showQuickScanner by remember { mutableStateOf(false) } fun loadItems() { scope.launch { @@ -46,6 +52,25 @@ fun AssignmentListScreen( loadItems() } + val filteredItems = remember(items, searchQuery) { + items.filter { + it.personName?.contains(searchQuery, ignoreCase = true) ?: false || + it.saiNs?.contains(searchQuery, ignoreCase = true) ?: false + } + } + + fun formatDate(dbDate: String?): String { + if (dbDate == null) return "N/A" + return try { + val dbFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val displayFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + val date = dbFormat.parse(dbDate) + if (date != null) displayFormat.format(date) else dbDate + } catch (e: Exception) { + dbDate + } + } + Scaffold( topBar = { TopAppBar( @@ -63,44 +88,59 @@ fun AssignmentListScreen( } } ) { padding -> - if (isLoading) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() + Column(modifier = Modifier.fillMaxSize().padding(padding)) { + SearchBar( + query = searchQuery, + onQueryChange = { searchQuery = it }, + onScanClick = { showQuickScanner = true }, + modifier = Modifier.padding(16.dp) + ) + + if (showQuickScanner) { + ScannerDialog( + onDismiss = { showQuickScanner = false }, + onCodeScanned = { code -> + searchQuery = code + showQuickScanner = false + } + ) } - } else { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(padding) - ) { - items(items) { item -> - ListItem( - headlineContent = { Text("${item.personName ?: "Persona desconocida"}") }, - supportingContent = { - Column { - Text("SAI: ${item.saiNs ?: "N/A"}") - Text("Asignado: ${item.assignedDate ?: "N/A"}") - if (item.returnDate != null) { - Text("Devolución: ${item.returnDate}") + + if (isLoading) { + Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } else { + LazyColumn(modifier = Modifier.weight(1f).fillMaxWidth()) { + items(filteredItems) { item -> + ListItem( + headlineContent = { Text(item.personName ?: "Persona desconocida") }, + supportingContent = { + Column { + Text("SAI: ${item.saiNs ?: "N/A"}") + Text("Asignado: ${formatDate(item.assignedDate)}") + if (item.returnDate != null) { + Text("Devolución: ${formatDate(item.returnDate)}") + } } - } - }, - trailingContent = { - Row { - IconButton(onClick = { onNavigateToEdit(item.id) }) { - Icon(Icons.Default.Edit, contentDescription = "Editar") - } - IconButton(onClick = { - scope.launch { - repository.delete(item.id).onSuccess { loadItems() } + }, + trailingContent = { + Row { + IconButton(onClick = { onNavigateToEdit(item.id) }) { + Icon(Icons.Default.Edit, contentDescription = "Editar") + } + IconButton(onClick = { + scope.launch { + repository.delete(item.id).onSuccess { loadItems() } + } + }) { + Icon(Icons.Default.Delete, contentDescription = "Eliminar") } - }) { - Icon(Icons.Default.Delete, contentDescription = "Eliminar") } } - } - ) - HorizontalDivider() + ) + HorizontalDivider() + } } } } diff --git a/app/src/main/java/com/example/saipp/ui/management/BatteryListScreen.kt b/app/src/main/java/com/example/saipp/ui/management/BatteryListScreen.kt index 63843ae..83a9f7b 100644 --- a/app/src/main/java/com/example/saipp/ui/management/BatteryListScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/BatteryListScreen.kt @@ -17,7 +17,10 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.example.saipp.data.BatteryRepository import com.example.saipp.data.model.Battery +import com.example.saipp.ui.SearchBar import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.* @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -31,6 +34,7 @@ fun BatteryListScreen( val repository = remember { BatteryRepository() } var items by remember { mutableStateOf>(emptyList()) } var isLoading by remember { mutableStateOf(true) } + var searchQuery by remember { mutableStateOf("") } fun loadItems() { scope.launch { @@ -46,6 +50,25 @@ fun BatteryListScreen( loadItems() } + val filteredItems = remember(items, searchQuery) { + items.filter { + it.brand?.contains(searchQuery, ignoreCase = true) ?: false || + it.idSai?.toString()?.contains(searchQuery) ?: false + } + } + + fun formatDate(dbDate: String?): String { + if (dbDate == null) return "N/A" + return try { + val dbFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val displayFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + val date = dbFormat.parse(dbDate) + if (date != null) displayFormat.format(date) else dbDate + } catch (e: Exception) { + dbDate + } + } + Scaffold( topBar = { TopAppBar( @@ -63,41 +86,45 @@ fun BatteryListScreen( } } ) { padding -> - if (isLoading) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } - } else { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(padding) - ) { - items(items) { item -> - ListItem( - headlineContent = { Text("Marca: ${item.brand ?: "Desconocida"}") }, - supportingContent = { - Column { - Text("Instalación: ${item.installDate ?: "N/A"}") - item.idSai?.let { Text("SAI ID: $it") } - } - }, - trailingContent = { - Row { - IconButton(onClick = { onNavigateToEdit(item.id) }) { - Icon(Icons.Default.Edit, contentDescription = "Editar") + Column(modifier = Modifier.fillMaxSize().padding(padding)) { + SearchBar( + query = searchQuery, + onQueryChange = { searchQuery = it }, + modifier = Modifier.padding(16.dp) + ) + + if (isLoading) { + Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } else { + LazyColumn(modifier = Modifier.weight(1f).fillMaxWidth()) { + items(filteredItems) { item -> + ListItem( + headlineContent = { Text("Marca: ${item.brand ?: "Desconocida"}") }, + supportingContent = { + Column { + Text("Instalación: ${formatDate(item.installDate)}") + item.idSai?.let { Text("SAI ID: $it") } } - IconButton(onClick = { - scope.launch { - repository.delete(item.id).onSuccess { loadItems() } + }, + trailingContent = { + Row { + IconButton(onClick = { onNavigateToEdit(item.id) }) { + Icon(Icons.Default.Edit, contentDescription = "Editar") + } + IconButton(onClick = { + scope.launch { + repository.delete(item.id).onSuccess { loadItems() } + } + }) { + Icon(Icons.Default.Delete, contentDescription = "Eliminar") } - }) { - Icon(Icons.Default.Delete, contentDescription = "Eliminar") } } - } - ) - HorizontalDivider() + ) + HorizontalDivider() + } } } } diff --git a/app/src/main/java/com/example/saipp/ui/management/EditAssignmentScreen.kt b/app/src/main/java/com/example/saipp/ui/management/EditAssignmentScreen.kt index e4594a3..4b6c700 100644 --- a/app/src/main/java/com/example/saipp/ui/management/EditAssignmentScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/EditAssignmentScreen.kt @@ -1,9 +1,12 @@ package com.example.saipp.ui.management import android.widget.Toast +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.CalendarMonth +import androidx.compose.material.icons.filled.QrCodeScanner import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -17,7 +20,10 @@ import com.example.saipp.data.SaiRepository import com.example.saipp.data.model.Assignment import com.example.saipp.data.model.Person import com.example.saipp.data.model.Sai +import com.example.saipp.ui.ScannerDialog import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.* @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -33,8 +39,11 @@ fun EditAssignmentScreen( var selectedSaiId by remember { mutableStateOf(null) } var selectedPersonId by remember { mutableStateOf(null) } - var assignedDate by remember { mutableStateOf("") } - var returnDate by remember { mutableStateOf("") } + + var assignedDateDisplay by remember { mutableStateOf("") } + var assignedDateDb by remember { mutableStateOf("") } + var returnDateDisplay by remember { mutableStateOf("") } + var returnDateDb by remember { mutableStateOf("") } var sais by remember { mutableStateOf>(emptyList()) } var people by remember { mutableStateOf>(emptyList()) } @@ -42,6 +51,13 @@ fun EditAssignmentScreen( var isSaving by remember { mutableStateOf(false) } var saiExpanded by remember { mutableStateOf(false) } var personExpanded by remember { mutableStateOf(false) } + + var showAssignedDatePicker by remember { mutableStateOf(false) } + var showReturnDatePicker by remember { mutableStateOf(false) } + var showQuickScanner by remember { mutableStateOf(false) } + + val assignedDatePickerState = rememberDatePickerState() + val returnDatePickerState = rememberDatePickerState() LaunchedEffect(assignmentId) { val assignmentResult = repository.getById(assignmentId) @@ -52,8 +68,32 @@ fun EditAssignmentScreen( val item = assignmentResult.getOrNull()!! selectedSaiId = item.idSai selectedPersonId = item.idPersona - assignedDate = item.assignedDate ?: "" - returnDate = item.returnDate ?: "" + + val dbFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val displayFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + + item.assignedDate?.let { dateStr -> + try { + val date = dbFormat.parse(dateStr) + if (date != null) { + assignedDateDisplay = displayFormat.format(date) + assignedDateDb = dateStr + assignedDatePickerState.selectedDateMillis = date.time + } + } catch (e: Exception) {} + } + + item.returnDate?.let { dateStr -> + try { + val date = dbFormat.parse(dateStr) + if (date != null) { + returnDateDisplay = displayFormat.format(date) + returnDateDb = dateStr + returnDatePickerState.selectedDateMillis = date.time + } + } catch (e: Exception) {} + } + sais = saisResult.getOrDefault(emptyList()) people = peopleResult.getOrDefault(emptyList()) isLoading = false @@ -63,6 +103,59 @@ fun EditAssignmentScreen( } } + if (showAssignedDatePicker) { + DatePickerDialog( + onDismissRequest = { showAssignedDatePicker = false }, + confirmButton = { + TextButton(onClick = { + assignedDatePickerState.selectedDateMillis?.let { millis -> + val date = Date(millis) + assignedDateDisplay = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(date) + assignedDateDb = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(date) + } + showAssignedDatePicker = false + }) { Text("Aceptar") } + }, + dismissButton = { + TextButton(onClick = { showAssignedDatePicker = false }) { Text("Cancelar") } + } + ) { DatePicker(state = assignedDatePickerState) } + } + + if (showReturnDatePicker) { + DatePickerDialog( + onDismissRequest = { showReturnDatePicker = false }, + confirmButton = { + TextButton(onClick = { + returnDatePickerState.selectedDateMillis?.let { millis -> + val date = Date(millis) + returnDateDisplay = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(date) + returnDateDb = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(date) + } + showReturnDatePicker = false + }) { Text("Aceptar") } + }, + dismissButton = { + TextButton(onClick = { showReturnDatePicker = false }) { Text("Cancelar") } + } + ) { DatePicker(state = returnDatePickerState) } + } + + if (showQuickScanner) { + ScannerDialog( + onDismiss = { showQuickScanner = false }, + onCodeScanned = { code -> + val matchingSai = sais.find { it.ns.equals(code, ignoreCase = true) } + if (matchingSai != null) { + selectedSaiId = matchingSai.id + } else { + Toast.makeText(context, "SAI no encontrado: $code", Toast.LENGTH_SHORT).show() + } + showQuickScanner = false + } + ) + } + Scaffold( topBar = { TopAppBar( @@ -97,6 +190,14 @@ fun EditAssignmentScreen( onValueChange = {}, readOnly = true, label = { Text(stringResource(R.string.label_sai)) }, + leadingIcon = { + IconButton(onClick = { showQuickScanner = true }) { + Icon( + imageVector = Icons.Default.QrCodeScanner, + contentDescription = "Escanear SAI" + ) + } + }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = saiExpanded) }, modifier = Modifier.menuAnchor().fillMaxWidth() ) @@ -151,19 +252,31 @@ fun EditAssignmentScreen( Spacer(modifier = Modifier.height(16.dp)) OutlinedTextField( - value = assignedDate, - onValueChange = { assignedDate = it }, + value = assignedDateDisplay, + onValueChange = {}, + readOnly = true, label = { Text(stringResource(R.string.label_assigned_date)) }, - modifier = Modifier.fillMaxWidth() + trailingIcon = { + IconButton(onClick = { showAssignedDatePicker = true }) { + Icon(Icons.Default.CalendarMonth, contentDescription = "Seleccionar fecha") + } + }, + modifier = Modifier.fillMaxWidth().clickable { showAssignedDatePicker = true } ) Spacer(modifier = Modifier.height(16.dp)) OutlinedTextField( - value = returnDate, - onValueChange = { returnDate = it }, + value = returnDateDisplay, + onValueChange = {}, + readOnly = true, label = { Text(stringResource(R.string.label_return_date)) }, - modifier = Modifier.fillMaxWidth() + trailingIcon = { + IconButton(onClick = { showReturnDatePicker = true }) { + Icon(Icons.Default.CalendarMonth, contentDescription = "Seleccionar fecha") + } + }, + modifier = Modifier.fillMaxWidth().clickable { showReturnDatePicker = true } ) Spacer(modifier = Modifier.height(32.dp)) @@ -178,8 +291,8 @@ fun EditAssignmentScreen( id = assignmentId, idSai = selectedSaiId!!, idPersona = selectedPersonId!!, - assignedDate = assignedDate.takeIf { it.isNotBlank() }, - returnDate = returnDate.takeIf { it.isNotBlank() } + assignedDate = assignedDateDb.takeIf { it.isNotBlank() }, + returnDate = returnDateDb.takeIf { it.isNotBlank() } ) ).onSuccess { Toast.makeText(context, "Asignación actualizada", Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/example/saipp/ui/management/EditBatteryScreen.kt b/app/src/main/java/com/example/saipp/ui/management/EditBatteryScreen.kt index 677215c..c05640f 100644 --- a/app/src/main/java/com/example/saipp/ui/management/EditBatteryScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/EditBatteryScreen.kt @@ -1,9 +1,12 @@ package com.example.saipp.ui.management import android.widget.Toast +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.CalendarMonth +import androidx.compose.material.icons.filled.QrCodeScanner import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -15,7 +18,10 @@ import com.example.saipp.data.BatteryRepository import com.example.saipp.data.SaiRepository import com.example.saipp.data.model.Battery import com.example.saipp.data.model.Sai +import com.example.saipp.ui.ScannerDialog import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.* @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -29,13 +35,18 @@ fun EditBatteryScreen( val saiRepository = remember { SaiRepository() } var brand by remember { mutableStateOf("") } - var installDate by remember { mutableStateOf("") } + var installDateDisplay by remember { mutableStateOf("") } + var installDateDb by remember { mutableStateOf("") } var selectedSaiId by remember { mutableStateOf(null) } var sais by remember { mutableStateOf>(emptyList()) } var isLoading by remember { mutableStateOf(true) } var isSaving by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) } + var showDatePicker by remember { mutableStateOf(false) } + var showQuickScanner by remember { mutableStateOf(false) } + + val datePickerState = rememberDatePickerState() LaunchedEffect(batteryId) { val batteryResult = batteryRepository.getById(batteryId) @@ -44,7 +55,24 @@ fun EditBatteryScreen( if (batteryResult.isSuccess) { val battery = batteryResult.getOrNull()!! brand = battery.brand ?: "" - installDate = battery.installDate ?: "" + + // Format existing date for display + battery.installDate?.let { dbDate -> + try { + val dbFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val displayFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + val date = dbFormat.parse(dbDate) + if (date != null) { + installDateDisplay = displayFormat.format(date) + installDateDb = dbDate + datePickerState.selectedDateMillis = date.time + } + } catch (e: Exception) { + installDateDisplay = dbDate + installDateDb = dbDate + } + } + selectedSaiId = battery.idSai sais = saisResult.getOrDefault(emptyList()) isLoading = false @@ -54,6 +82,48 @@ fun EditBatteryScreen( } } + if (showDatePicker) { + DatePickerDialog( + onDismissRequest = { showDatePicker = false }, + confirmButton = { + TextButton(onClick = { + datePickerState.selectedDateMillis?.let { millis -> + val date = Date(millis) + val displayFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + val dbFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + installDateDisplay = displayFormat.format(date) + installDateDb = dbFormat.format(date) + } + showDatePicker = false + }) { + Text("Aceptar") + } + }, + dismissButton = { + TextButton(onClick = { showDatePicker = false }) { + Text("Cancelar") + } + } + ) { + DatePicker(state = datePickerState) + } + } + + if (showQuickScanner) { + ScannerDialog( + onDismiss = { showQuickScanner = false }, + onCodeScanned = { code -> + val matchingSai = sais.find { it.ns.equals(code, ignoreCase = true) } + if (matchingSai != null) { + selectedSaiId = matchingSai.id + } else { + Toast.makeText(context, "SAI no encontrado: $code", Toast.LENGTH_SHORT).show() + } + showQuickScanner = false + } + ) + } + Scaffold( topBar = { TopAppBar( @@ -88,6 +158,14 @@ fun EditBatteryScreen( onValueChange = {}, readOnly = true, label = { Text(stringResource(R.string.label_sai)) }, + leadingIcon = { + IconButton(onClick = { showQuickScanner = true }) { + Icon( + imageVector = Icons.Default.QrCodeScanner, + contentDescription = "Escanear SAI" + ) + } + }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, modifier = Modifier.menuAnchor().fillMaxWidth() ) @@ -126,10 +204,18 @@ fun EditBatteryScreen( Spacer(modifier = Modifier.height(16.dp)) OutlinedTextField( - value = installDate, - onValueChange = { installDate = it }, + value = installDateDisplay, + onValueChange = {}, + readOnly = true, label = { Text(stringResource(R.string.label_install_date)) }, - modifier = Modifier.fillMaxWidth() + trailingIcon = { + IconButton(onClick = { showDatePicker = true }) { + Icon(Icons.Default.CalendarMonth, contentDescription = "Seleccionar fecha") + } + }, + modifier = Modifier + .fillMaxWidth() + .clickable { showDatePicker = true } ) Spacer(modifier = Modifier.height(32.dp)) @@ -143,7 +229,7 @@ fun EditBatteryScreen( id = batteryId, idSai = selectedSaiId, brand = brand, - installDate = installDate.takeIf { it.isNotBlank() } + installDate = installDateDb.takeIf { it.isNotBlank() } ) ).onSuccess { Toast.makeText(context, "Batería actualizada", Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/example/saipp/ui/management/EditSaiScreen.kt b/app/src/main/java/com/example/saipp/ui/management/EditSaiScreen.kt index f4747e3..2f680d6 100644 --- a/app/src/main/java/com/example/saipp/ui/management/EditSaiScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/EditSaiScreen.kt @@ -8,7 +8,9 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.example.saipp.R import com.example.saipp.data.SaiRepository import com.example.saipp.data.model.Sai import kotlinx.coroutines.launch @@ -24,12 +26,14 @@ fun EditSaiScreen( val repository = remember { SaiRepository() } var ns by remember { mutableStateOf("") } + var modelo by remember { mutableStateOf("") } var isLoading by remember { mutableStateOf(true) } var isSaving by remember { mutableStateOf(false) } LaunchedEffect(saiId) { repository.getById(saiId).onSuccess { ns = it.ns + modelo = it.modelo ?: "" isLoading = false }.onFailure { Toast.makeText(context, "Error al cargar SAI", Toast.LENGTH_SHORT).show() @@ -66,13 +70,20 @@ fun EditSaiScreen( label = { Text("Número de Serie") }, modifier = Modifier.fillMaxWidth() ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = modelo, + onValueChange = { modelo = it }, + label = { Text(stringResource(R.string.label_modelo)) }, + modifier = Modifier.fillMaxWidth() + ) Spacer(modifier = Modifier.height(32.dp)) Button( onClick = { if (ns.isNotBlank()) { isSaving = true scope.launch { - repository.update(Sai(id = saiId, ns = ns)) + repository.update(Sai(id = saiId, ns = ns, modelo = modelo)) .onSuccess { Toast.makeText(context, "SAI actualizado", Toast.LENGTH_SHORT).show() onNavigateBack() diff --git a/app/src/main/java/com/example/saipp/ui/management/PersonListScreen.kt b/app/src/main/java/com/example/saipp/ui/management/PersonListScreen.kt index 29d8025..1c2eca4 100644 --- a/app/src/main/java/com/example/saipp/ui/management/PersonListScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/PersonListScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.example.saipp.data.PersonRepository import com.example.saipp.data.model.Person +import com.example.saipp.ui.SearchBar import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -31,6 +32,7 @@ fun PersonListScreen( val repository = remember { PersonRepository() } var items by remember { mutableStateOf>(emptyList()) } var isLoading by remember { mutableStateOf(true) } + var searchQuery by remember { mutableStateOf("") } fun loadItems() { scope.launch { @@ -46,6 +48,10 @@ fun PersonListScreen( loadItems() } + val filteredItems = remember(items, searchQuery) { + items.filter { it.name.contains(searchQuery, ignoreCase = true) } + } + Scaffold( topBar = { TopAppBar( @@ -63,35 +69,39 @@ fun PersonListScreen( } } ) { padding -> - if (isLoading) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } - } else { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(padding) - ) { - items(items) { item -> - ListItem( - headlineContent = { Text(item.name) }, - trailingContent = { - Row { - IconButton(onClick = { onNavigateToEdit(item.id) }) { - Icon(Icons.Default.Edit, contentDescription = "Editar") - } - IconButton(onClick = { - scope.launch { - repository.delete(item.id).onSuccess { loadItems() } + Column(modifier = Modifier.fillMaxSize().padding(padding)) { + SearchBar( + query = searchQuery, + onQueryChange = { searchQuery = it }, + modifier = Modifier.padding(16.dp) + ) + + if (isLoading) { + Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } else { + LazyColumn(modifier = Modifier.weight(1f).fillMaxWidth()) { + items(filteredItems) { item -> + ListItem( + headlineContent = { Text(item.name) }, + trailingContent = { + Row { + IconButton(onClick = { onNavigateToEdit(item.id) }) { + Icon(Icons.Default.Edit, contentDescription = "Editar") + } + IconButton(onClick = { + scope.launch { + repository.delete(item.id).onSuccess { loadItems() } + } + }) { + Icon(Icons.Default.Delete, contentDescription = "Eliminar") } - }) { - Icon(Icons.Default.Delete, contentDescription = "Eliminar") } } - } - ) - HorizontalDivider() + ) + HorizontalDivider() + } } } } diff --git a/app/src/main/java/com/example/saipp/ui/management/SaiListScreen.kt b/app/src/main/java/com/example/saipp/ui/management/SaiListScreen.kt index 2964a65..7cdb6c4 100644 --- a/app/src/main/java/com/example/saipp/ui/management/SaiListScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/SaiListScreen.kt @@ -17,6 +17,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.example.saipp.data.SaiRepository import com.example.saipp.data.model.Sai +import com.example.saipp.ui.ScannerDialog +import com.example.saipp.ui.SearchBar import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -31,6 +33,8 @@ fun SaiListScreen( val repository = remember { SaiRepository() } var sais by remember { mutableStateOf>(emptyList()) } var isLoading by remember { mutableStateOf(true) } + var searchQuery by remember { mutableStateOf("") } + var showQuickScanner by remember { mutableStateOf(false) } fun loadSais() { scope.launch { @@ -46,6 +50,13 @@ fun SaiListScreen( loadSais() } + val filteredSais = remember(sais, searchQuery) { + sais.filter { + it.ns.contains(searchQuery, ignoreCase = true) || + (it.modelo?.contains(searchQuery, ignoreCase = true) ?: false) + } + } + Scaffold( topBar = { TopAppBar( @@ -63,35 +74,51 @@ fun SaiListScreen( } } ) { padding -> - if (isLoading) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() + Column(modifier = Modifier.fillMaxSize().padding(padding)) { + SearchBar( + query = searchQuery, + onQueryChange = { searchQuery = it }, + modifier = Modifier.padding(16.dp), + onScanClick = { showQuickScanner = true } + ) + + if (showQuickScanner) { + ScannerDialog( + onDismiss = { showQuickScanner = false }, + onCodeScanned = { code -> + searchQuery = code + showQuickScanner = false + } + ) } - } else { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(padding) - ) { - items(sais) { sai -> - ListItem( - headlineContent = { Text("NS: ${sai.ns}") }, - trailingContent = { - Row { - IconButton(onClick = { onNavigateToEdit(sai.id) }) { - Icon(Icons.Default.Edit, contentDescription = "Editar") - } - IconButton(onClick = { - scope.launch { - repository.delete(sai.id).onSuccess { loadSais() } + + if (isLoading) { + Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } else { + LazyColumn(modifier = Modifier.weight(1f).fillMaxWidth()) { + items(filteredSais) { sai -> + ListItem( + headlineContent = { Text("NS: ${sai.ns}") }, + supportingContent = { Text("Modelo: ${sai.modelo ?: "N/A"}") }, + trailingContent = { + Row { + IconButton(onClick = { onNavigateToEdit(sai.id) }) { + Icon(Icons.Default.Edit, contentDescription = "Editar") + } + IconButton(onClick = { + scope.launch { + repository.delete(sai.id).onSuccess { loadSais() } + } + }) { + Icon(Icons.Default.Delete, contentDescription = "Eliminar") } - }) { - Icon(Icons.Default.Delete, contentDescription = "Eliminar") } } - } - ) - HorizontalDivider() + ) + HorizontalDivider() + } } } } diff --git a/app/src/main/java/com/example/saipp/ui/management/SaiScannerScreen.kt b/app/src/main/java/com/example/saipp/ui/management/SaiScannerScreen.kt index cd8852c..7afe0c8 100644 --- a/app/src/main/java/com/example/saipp/ui/management/SaiScannerScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/SaiScannerScreen.kt @@ -2,13 +2,10 @@ package com.example.saipp.ui.management import android.Manifest import android.content.pm.PackageManager -import android.util.Log import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.* @@ -16,9 +13,10 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat -import com.example.saipp.ui.ResultDialog +import com.example.saipp.R import com.example.saipp.data.SaiRepository import com.example.saipp.ui.scanner.BarcodeScannerView import kotlinx.coroutines.launch @@ -55,6 +53,7 @@ fun SaiScannerScreen( } var scannedBarcode by remember { mutableStateOf(null) } + var modelo by remember { mutableStateOf("") } var isSaving by remember { mutableStateOf(false) } Scaffold( @@ -91,20 +90,48 @@ fun SaiScannerScreen( } scannedBarcode?.let { barcode -> - ResultDialog( - barcode = barcode, - onDismiss = { scannedBarcode = null }, - onConfirm = { - scannedBarcode = null - isSaving = true - scope.launch { - repository.insert(barcode) - .onSuccess { - Toast.makeText(context, "SAI guardado", Toast.LENGTH_SHORT).show() - onNavigateBack() + AlertDialog( + onDismissRequest = { scannedBarcode = null }, + title = { Text(text = stringResource(R.string.dialog_title)) }, + text = { + Column { + Text(text = "Código: $barcode") + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = modelo, + onValueChange = { modelo = it }, + label = { Text(stringResource(R.string.label_modelo)) }, + modifier = Modifier.fillMaxWidth() + ) + } + }, + confirmButton = { + Button(onClick = { + val codeToSave = scannedBarcode!! + scannedBarcode = null + isSaving = true + scope.launch { + // Check for duplicates + val isRegisteredResult = repository.isNsRegistered(codeToSave) + if (isRegisteredResult.getOrDefault(false)) { + Toast.makeText(context, R.string.error_duplicate_sai, Toast.LENGTH_LONG).show() + } else { + repository.insert(codeToSave, modelo) + .onSuccess { + Toast.makeText(context, "SAI guardado", Toast.LENGTH_SHORT).show() + onNavigateBack() + } + .onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() } } - .onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() } - isSaving = false + isSaving = false + } + }) { + Text(stringResource(R.string.button_save)) + } + }, + dismissButton = { + TextButton(onClick = { scannedBarcode = null }) { + Text(stringResource(R.string.button_close)) } } ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8447b73..7ee2cc2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,9 +15,13 @@ No se insertaron filas en la base de datos SAI Marca - Fecha Instalación (AAAA-MM-DD HH:MM:SS) + Modelo + Fecha Instalación (DD-MM-AAAA) Seleccionar SAI Ninguno + Buscar... + Este SAI ya está registrado en el sistema. + Escanear para buscar Gestión de SAIs Gestión de Baterías @@ -25,7 +29,7 @@ Gestión de Asignaciones Persona - Fecha Asignación (AAAA-MM-DD) - Fecha Devolución (AAAA-MM-DD) + Fecha Asignación (DD-MM-AAAA) + Fecha Devolución (DD-MM-AAAA) Seleccionar Persona \ No newline at end of file