Aplicacion terminada

This commit is contained in:
2026-07-31 13:58:56 +02:00
parent 2d185416c6
commit eb8cc806c1
16 changed files with 782 additions and 192 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
+2 -1
View File
@@ -39,7 +39,8 @@ Ensure your MariaDB database has the following tables:
-- Table for SAIs -- Table for SAIs
CREATE TABLE SAIs ( CREATE TABLE SAIs (
id_sai INT AUTO_INCREMENT PRIMARY KEY, id_sai INT AUTO_INCREMENT PRIMARY KEY,
ns VARCHAR(255) NOT NULL ns VARCHAR(255) NOT NULL UNIQUE,
modelo VARCHAR(255)
); );
-- Table for Batteries -- Table for Batteries
@@ -1,25 +1,24 @@
package com.example.saipp.data package com.example.saipp.data
import android.util.Log
import com.example.saipp.data.model.Sai import com.example.saipp.data.model.Sai
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.sql.SQLException import java.sql.SQLException
class SaiRepository { class SaiRepository {
companion object {
private const val TAG = "SaiRepository"
}
suspend fun getAll(): Result<List<Sai>> = withContext(Dispatchers.IO) { suspend fun getAll(): Result<List<Sai>> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try { try {
val sql = "SELECT id_sai, ns FROM SAIs" val sql = "SELECT id_sai, ns, modelo FROM SAIs"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
val resultSet = statement.executeQuery() val resultSet = statement.executeQuery()
val sais = mutableListOf<Sai>() val sais = mutableListOf<Sai>()
while (resultSet.next()) { 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) Result.success(sais)
} catch (e: SQLException) { } catch (e: SQLException) {
@@ -29,12 +28,32 @@ class SaiRepository {
} }
} }
suspend fun insert(ns: String): Result<Unit> = withContext(Dispatchers.IO) { suspend fun isNsRegistered(ns: String): Result<Boolean> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try { try {
val sql = "INSERT INTO SAIs (ns) VALUES (?)" val sql = "SELECT COUNT(*) FROM SAIs WHERE ns = ?"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
statement.setString(1, ns) 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<Unit> = 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) if (statement.executeUpdate() > 0) Result.success(Unit)
else Result.failure(Exception("no_rows_inserted")) else Result.failure(Exception("no_rows_inserted"))
} catch (e: SQLException) { } catch (e: SQLException) {
@@ -47,10 +66,11 @@ class SaiRepository {
suspend fun update(sai: Sai): Result<Unit> = withContext(Dispatchers.IO) { suspend fun update(sai: Sai): Result<Unit> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try { 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) val statement = connection.prepareStatement(sql)
statement.setString(1, sai.ns) 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) if (statement.executeUpdate() > 0) Result.success(Unit)
else Result.failure(Exception("update_failed")) else Result.failure(Exception("update_failed"))
} catch (e: SQLException) { } catch (e: SQLException) {
@@ -78,12 +98,16 @@ class SaiRepository {
suspend fun getById(id: Int): Result<Sai> = withContext(Dispatchers.IO) { suspend fun getById(id: Int): Result<Sai> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try { 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) val statement = connection.prepareStatement(sql)
statement.setInt(1, id) statement.setInt(1, id)
val resultSet = statement.executeQuery() val resultSet = statement.executeQuery()
if (resultSet.next()) { 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 { } else {
Result.failure(Exception("not_found")) Result.failure(Exception("not_found"))
} }
@@ -2,5 +2,6 @@ package com.example.saipp.data.model
data class Sai( data class Sai(
val id: Int = 0, val id: Int = 0,
val ns: String val ns: String,
val modelo: String? = null
) )
@@ -2,19 +2,80 @@ package com.example.saipp.ui
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.AlertDialog import androidx.compose.material.icons.Icons
import androidx.compose.material3.Button import androidx.compose.material.icons.filled.QrCodeScanner
import androidx.compose.material3.MaterialTheme import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Surface import androidx.compose.material3.*
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp 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.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 @Composable
fun ResultDialog( fun ResultDialog(
@@ -1,9 +1,12 @@
package com.example.saipp.ui.management package com.example.saipp.ui.management
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack 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.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier 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.Assignment
import com.example.saipp.data.model.Person import com.example.saipp.data.model.Person
import com.example.saipp.data.model.Sai import com.example.saipp.data.model.Sai
import com.example.saipp.ui.ScannerDialog
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -32,8 +38,11 @@ fun AddAssignmentScreen(
var selectedSaiId by remember { mutableStateOf<Int?>(null) } var selectedSaiId by remember { mutableStateOf<Int?>(null) }
var selectedPersonId by remember { mutableStateOf<Int?>(null) } var selectedPersonId by remember { mutableStateOf<Int?>(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<List<Sai>>(emptyList()) } var sais by remember { mutableStateOf<List<Sai>>(emptyList()) }
var people by remember { mutableStateOf<List<Person>>(emptyList()) } var people by remember { mutableStateOf<List<Person>>(emptyList()) }
@@ -41,11 +50,71 @@ fun AddAssignmentScreen(
var saiExpanded by remember { mutableStateOf(false) } var saiExpanded by remember { mutableStateOf(false) }
var personExpanded 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) { LaunchedEffect(Unit) {
saiRepository.getAll().onSuccess { sais = it } saiRepository.getAll().onSuccess { sais = it }
personRepository.getAll().onSuccess { people = 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( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -75,6 +144,14 @@ fun AddAssignmentScreen(
onValueChange = {}, onValueChange = {},
readOnly = true, readOnly = true,
label = { Text(stringResource(R.string.label_sai)) }, 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) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = saiExpanded) },
modifier = Modifier.menuAnchor().fillMaxWidth() modifier = Modifier.menuAnchor().fillMaxWidth()
) )
@@ -129,19 +206,31 @@ fun AddAssignmentScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField( OutlinedTextField(
value = assignedDate, value = assignedDateDisplay,
onValueChange = { assignedDate = it }, onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_assigned_date)) }, 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)) Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField( OutlinedTextField(
value = returnDate, value = returnDateDisplay,
onValueChange = { returnDate = it }, onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_return_date)) }, 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)) Spacer(modifier = Modifier.height(32.dp))
@@ -155,8 +244,8 @@ fun AddAssignmentScreen(
Assignment( Assignment(
idSai = selectedSaiId!!, idSai = selectedSaiId!!,
idPersona = selectedPersonId!!, idPersona = selectedPersonId!!,
assignedDate = assignedDate.takeIf { it.isNotBlank() }, assignedDate = assignedDateDb.takeIf { it.isNotBlank() },
returnDate = returnDate.takeIf { it.isNotBlank() } returnDate = returnDateDb.takeIf { it.isNotBlank() }
) )
).onSuccess { ).onSuccess {
Toast.makeText(context, "Asignación guardada", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Asignación guardada", Toast.LENGTH_SHORT).show()
@@ -1,9 +1,12 @@
package com.example.saipp.ui.management package com.example.saipp.ui.management
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack 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.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier 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.SaiRepository
import com.example.saipp.data.model.Battery import com.example.saipp.data.model.Battery
import com.example.saipp.data.model.Sai import com.example.saipp.data.model.Sai
import com.example.saipp.ui.ScannerDialog
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -28,17 +34,64 @@ fun AddBatteryScreen(
val saiRepository = remember { SaiRepository() } val saiRepository = remember { SaiRepository() }
var brand by remember { mutableStateOf("") } 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<Int?>(null) } var selectedSaiId by remember { mutableStateOf<Int?>(null) }
var sais by remember { mutableStateOf<List<Sai>>(emptyList()) } var sais by remember { mutableStateOf<List<Sai>>(emptyList()) }
var isSaving by remember { mutableStateOf(false) } var isSaving by remember { mutableStateOf(false) }
var expanded 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) { LaunchedEffect(Unit) {
saiRepository.getAll().onSuccess { sais = it } 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( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -68,6 +121,14 @@ fun AddBatteryScreen(
onValueChange = {}, onValueChange = {},
readOnly = true, readOnly = true,
label = { Text(stringResource(R.string.label_sai)) }, 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) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier.menuAnchor().fillMaxWidth() modifier = Modifier.menuAnchor().fillMaxWidth()
) )
@@ -106,10 +167,18 @@ fun AddBatteryScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField( OutlinedTextField(
value = installDate, value = installDateDisplay,
onValueChange = { installDate = it }, onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_install_date)) }, 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)) Spacer(modifier = Modifier.height(32.dp))
@@ -122,7 +191,7 @@ fun AddBatteryScreen(
Battery( Battery(
idSai = selectedSaiId, idSai = selectedSaiId,
brand = brand, brand = brand,
installDate = installDate.takeIf { it.isNotBlank() } installDate = installDateDb.takeIf { it.isNotBlank() }
) )
).onSuccess { ).onSuccess {
Toast.makeText(context, "Batería añadida", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Batería añadida", Toast.LENGTH_SHORT).show()
@@ -17,7 +17,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.saipp.data.AssignmentRepository import com.example.saipp.data.AssignmentRepository
import com.example.saipp.data.model.Assignment import com.example.saipp.data.model.Assignment
import com.example.saipp.ui.ScannerDialog
import com.example.saipp.ui.SearchBar
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -31,6 +35,8 @@ fun AssignmentListScreen(
val repository = remember { AssignmentRepository() } val repository = remember { AssignmentRepository() }
var items by remember { mutableStateOf<List<Assignment>>(emptyList()) } var items by remember { mutableStateOf<List<Assignment>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) } var isLoading by remember { mutableStateOf(true) }
var searchQuery by remember { mutableStateOf("") }
var showQuickScanner by remember { mutableStateOf(false) }
fun loadItems() { fun loadItems() {
scope.launch { scope.launch {
@@ -46,6 +52,25 @@ fun AssignmentListScreen(
loadItems() 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( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -63,25 +88,39 @@ fun AssignmentListScreen(
} }
} }
) { padding -> ) { padding ->
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
}
)
}
if (isLoading) { if (isLoading) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
CircularProgressIndicator() CircularProgressIndicator()
} }
} else { } else {
LazyColumn( LazyColumn(modifier = Modifier.weight(1f).fillMaxWidth()) {
modifier = Modifier items(filteredItems) { item ->
.fillMaxSize()
.padding(padding)
) {
items(items) { item ->
ListItem( ListItem(
headlineContent = { Text("${item.personName ?: "Persona desconocida"}") }, headlineContent = { Text(item.personName ?: "Persona desconocida") },
supportingContent = { supportingContent = {
Column { Column {
Text("SAI: ${item.saiNs ?: "N/A"}") Text("SAI: ${item.saiNs ?: "N/A"}")
Text("Asignado: ${item.assignedDate ?: "N/A"}") Text("Asignado: ${formatDate(item.assignedDate)}")
if (item.returnDate != null) { if (item.returnDate != null) {
Text("Devolución: ${item.returnDate}") Text("Devolución: ${formatDate(item.returnDate)}")
} }
} }
}, },
@@ -106,3 +145,4 @@ fun AssignmentListScreen(
} }
} }
} }
}
@@ -17,7 +17,10 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.saipp.data.BatteryRepository import com.example.saipp.data.BatteryRepository
import com.example.saipp.data.model.Battery import com.example.saipp.data.model.Battery
import com.example.saipp.ui.SearchBar
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -31,6 +34,7 @@ fun BatteryListScreen(
val repository = remember { BatteryRepository() } val repository = remember { BatteryRepository() }
var items by remember { mutableStateOf<List<Battery>>(emptyList()) } var items by remember { mutableStateOf<List<Battery>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) } var isLoading by remember { mutableStateOf(true) }
var searchQuery by remember { mutableStateOf("") }
fun loadItems() { fun loadItems() {
scope.launch { scope.launch {
@@ -46,6 +50,25 @@ fun BatteryListScreen(
loadItems() 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( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -63,22 +86,25 @@ fun BatteryListScreen(
} }
} }
) { padding -> ) { padding ->
Column(modifier = Modifier.fillMaxSize().padding(padding)) {
SearchBar(
query = searchQuery,
onQueryChange = { searchQuery = it },
modifier = Modifier.padding(16.dp)
)
if (isLoading) { if (isLoading) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
CircularProgressIndicator() CircularProgressIndicator()
} }
} else { } else {
LazyColumn( LazyColumn(modifier = Modifier.weight(1f).fillMaxWidth()) {
modifier = Modifier items(filteredItems) { item ->
.fillMaxSize()
.padding(padding)
) {
items(items) { item ->
ListItem( ListItem(
headlineContent = { Text("Marca: ${item.brand ?: "Desconocida"}") }, headlineContent = { Text("Marca: ${item.brand ?: "Desconocida"}") },
supportingContent = { supportingContent = {
Column { Column {
Text("Instalación: ${item.installDate ?: "N/A"}") Text("Instalación: ${formatDate(item.installDate)}")
item.idSai?.let { Text("SAI ID: $it") } item.idSai?.let { Text("SAI ID: $it") }
} }
}, },
@@ -103,3 +129,4 @@ fun BatteryListScreen(
} }
} }
} }
}
@@ -1,9 +1,12 @@
package com.example.saipp.ui.management package com.example.saipp.ui.management
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack 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.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier 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.Assignment
import com.example.saipp.data.model.Person import com.example.saipp.data.model.Person
import com.example.saipp.data.model.Sai import com.example.saipp.data.model.Sai
import com.example.saipp.ui.ScannerDialog
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -33,8 +39,11 @@ fun EditAssignmentScreen(
var selectedSaiId by remember { mutableStateOf<Int?>(null) } var selectedSaiId by remember { mutableStateOf<Int?>(null) }
var selectedPersonId by remember { mutableStateOf<Int?>(null) } var selectedPersonId by remember { mutableStateOf<Int?>(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<List<Sai>>(emptyList()) } var sais by remember { mutableStateOf<List<Sai>>(emptyList()) }
var people by remember { mutableStateOf<List<Person>>(emptyList()) } var people by remember { mutableStateOf<List<Person>>(emptyList()) }
@@ -43,6 +52,13 @@ fun EditAssignmentScreen(
var saiExpanded by remember { mutableStateOf(false) } var saiExpanded by remember { mutableStateOf(false) }
var personExpanded 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) { LaunchedEffect(assignmentId) {
val assignmentResult = repository.getById(assignmentId) val assignmentResult = repository.getById(assignmentId)
val saisResult = saiRepository.getAll() val saisResult = saiRepository.getAll()
@@ -52,8 +68,32 @@ fun EditAssignmentScreen(
val item = assignmentResult.getOrNull()!! val item = assignmentResult.getOrNull()!!
selectedSaiId = item.idSai selectedSaiId = item.idSai
selectedPersonId = item.idPersona 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()) sais = saisResult.getOrDefault(emptyList())
people = peopleResult.getOrDefault(emptyList()) people = peopleResult.getOrDefault(emptyList())
isLoading = false 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( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -97,6 +190,14 @@ fun EditAssignmentScreen(
onValueChange = {}, onValueChange = {},
readOnly = true, readOnly = true,
label = { Text(stringResource(R.string.label_sai)) }, 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) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = saiExpanded) },
modifier = Modifier.menuAnchor().fillMaxWidth() modifier = Modifier.menuAnchor().fillMaxWidth()
) )
@@ -151,19 +252,31 @@ fun EditAssignmentScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField( OutlinedTextField(
value = assignedDate, value = assignedDateDisplay,
onValueChange = { assignedDate = it }, onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_assigned_date)) }, 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)) Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField( OutlinedTextField(
value = returnDate, value = returnDateDisplay,
onValueChange = { returnDate = it }, onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_return_date)) }, 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)) Spacer(modifier = Modifier.height(32.dp))
@@ -178,8 +291,8 @@ fun EditAssignmentScreen(
id = assignmentId, id = assignmentId,
idSai = selectedSaiId!!, idSai = selectedSaiId!!,
idPersona = selectedPersonId!!, idPersona = selectedPersonId!!,
assignedDate = assignedDate.takeIf { it.isNotBlank() }, assignedDate = assignedDateDb.takeIf { it.isNotBlank() },
returnDate = returnDate.takeIf { it.isNotBlank() } returnDate = returnDateDb.takeIf { it.isNotBlank() }
) )
).onSuccess { ).onSuccess {
Toast.makeText(context, "Asignación actualizada", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Asignación actualizada", Toast.LENGTH_SHORT).show()
@@ -1,9 +1,12 @@
package com.example.saipp.ui.management package com.example.saipp.ui.management
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack 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.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier 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.SaiRepository
import com.example.saipp.data.model.Battery import com.example.saipp.data.model.Battery
import com.example.saipp.data.model.Sai import com.example.saipp.data.model.Sai
import com.example.saipp.ui.ScannerDialog
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -29,13 +35,18 @@ fun EditBatteryScreen(
val saiRepository = remember { SaiRepository() } val saiRepository = remember { SaiRepository() }
var brand by remember { mutableStateOf("") } 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<Int?>(null) } var selectedSaiId by remember { mutableStateOf<Int?>(null) }
var sais by remember { mutableStateOf<List<Sai>>(emptyList()) } var sais by remember { mutableStateOf<List<Sai>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) } var isLoading by remember { mutableStateOf(true) }
var isSaving by remember { mutableStateOf(false) } var isSaving by remember { mutableStateOf(false) }
var expanded 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) { LaunchedEffect(batteryId) {
val batteryResult = batteryRepository.getById(batteryId) val batteryResult = batteryRepository.getById(batteryId)
@@ -44,7 +55,24 @@ fun EditBatteryScreen(
if (batteryResult.isSuccess) { if (batteryResult.isSuccess) {
val battery = batteryResult.getOrNull()!! val battery = batteryResult.getOrNull()!!
brand = battery.brand ?: "" 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 selectedSaiId = battery.idSai
sais = saisResult.getOrDefault(emptyList()) sais = saisResult.getOrDefault(emptyList())
isLoading = false 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( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -88,6 +158,14 @@ fun EditBatteryScreen(
onValueChange = {}, onValueChange = {},
readOnly = true, readOnly = true,
label = { Text(stringResource(R.string.label_sai)) }, 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) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier.menuAnchor().fillMaxWidth() modifier = Modifier.menuAnchor().fillMaxWidth()
) )
@@ -126,10 +204,18 @@ fun EditBatteryScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField( OutlinedTextField(
value = installDate, value = installDateDisplay,
onValueChange = { installDate = it }, onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_install_date)) }, 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)) Spacer(modifier = Modifier.height(32.dp))
@@ -143,7 +229,7 @@ fun EditBatteryScreen(
id = batteryId, id = batteryId,
idSai = selectedSaiId, idSai = selectedSaiId,
brand = brand, brand = brand,
installDate = installDate.takeIf { it.isNotBlank() } installDate = installDateDb.takeIf { it.isNotBlank() }
) )
).onSuccess { ).onSuccess {
Toast.makeText(context, "Batería actualizada", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Batería actualizada", Toast.LENGTH_SHORT).show()
@@ -8,7 +8,9 @@ import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.saipp.R
import com.example.saipp.data.SaiRepository import com.example.saipp.data.SaiRepository
import com.example.saipp.data.model.Sai import com.example.saipp.data.model.Sai
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -24,12 +26,14 @@ fun EditSaiScreen(
val repository = remember { SaiRepository() } val repository = remember { SaiRepository() }
var ns by remember { mutableStateOf("") } var ns by remember { mutableStateOf("") }
var modelo by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(true) } var isLoading by remember { mutableStateOf(true) }
var isSaving by remember { mutableStateOf(false) } var isSaving by remember { mutableStateOf(false) }
LaunchedEffect(saiId) { LaunchedEffect(saiId) {
repository.getById(saiId).onSuccess { repository.getById(saiId).onSuccess {
ns = it.ns ns = it.ns
modelo = it.modelo ?: ""
isLoading = false isLoading = false
}.onFailure { }.onFailure {
Toast.makeText(context, "Error al cargar SAI", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Error al cargar SAI", Toast.LENGTH_SHORT).show()
@@ -66,13 +70,20 @@ fun EditSaiScreen(
label = { Text("Número de Serie") }, label = { Text("Número de Serie") },
modifier = Modifier.fillMaxWidth() 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)) Spacer(modifier = Modifier.height(32.dp))
Button( Button(
onClick = { onClick = {
if (ns.isNotBlank()) { if (ns.isNotBlank()) {
isSaving = true isSaving = true
scope.launch { scope.launch {
repository.update(Sai(id = saiId, ns = ns)) repository.update(Sai(id = saiId, ns = ns, modelo = modelo))
.onSuccess { .onSuccess {
Toast.makeText(context, "SAI actualizado", Toast.LENGTH_SHORT).show() Toast.makeText(context, "SAI actualizado", Toast.LENGTH_SHORT).show()
onNavigateBack() onNavigateBack()
@@ -17,6 +17,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.saipp.data.PersonRepository import com.example.saipp.data.PersonRepository
import com.example.saipp.data.model.Person import com.example.saipp.data.model.Person
import com.example.saipp.ui.SearchBar
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -31,6 +32,7 @@ fun PersonListScreen(
val repository = remember { PersonRepository() } val repository = remember { PersonRepository() }
var items by remember { mutableStateOf<List<Person>>(emptyList()) } var items by remember { mutableStateOf<List<Person>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) } var isLoading by remember { mutableStateOf(true) }
var searchQuery by remember { mutableStateOf("") }
fun loadItems() { fun loadItems() {
scope.launch { scope.launch {
@@ -46,6 +48,10 @@ fun PersonListScreen(
loadItems() loadItems()
} }
val filteredItems = remember(items, searchQuery) {
items.filter { it.name.contains(searchQuery, ignoreCase = true) }
}
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -63,17 +69,20 @@ fun PersonListScreen(
} }
} }
) { padding -> ) { padding ->
Column(modifier = Modifier.fillMaxSize().padding(padding)) {
SearchBar(
query = searchQuery,
onQueryChange = { searchQuery = it },
modifier = Modifier.padding(16.dp)
)
if (isLoading) { if (isLoading) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
CircularProgressIndicator() CircularProgressIndicator()
} }
} else { } else {
LazyColumn( LazyColumn(modifier = Modifier.weight(1f).fillMaxWidth()) {
modifier = Modifier items(filteredItems) { item ->
.fillMaxSize()
.padding(padding)
) {
items(items) { item ->
ListItem( ListItem(
headlineContent = { Text(item.name) }, headlineContent = { Text(item.name) },
trailingContent = { trailingContent = {
@@ -97,3 +106,4 @@ fun PersonListScreen(
} }
} }
} }
}
@@ -17,6 +17,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.saipp.data.SaiRepository import com.example.saipp.data.SaiRepository
import com.example.saipp.data.model.Sai import com.example.saipp.data.model.Sai
import com.example.saipp.ui.ScannerDialog
import com.example.saipp.ui.SearchBar
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -31,6 +33,8 @@ fun SaiListScreen(
val repository = remember { SaiRepository() } val repository = remember { SaiRepository() }
var sais by remember { mutableStateOf<List<Sai>>(emptyList()) } var sais by remember { mutableStateOf<List<Sai>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) } var isLoading by remember { mutableStateOf(true) }
var searchQuery by remember { mutableStateOf("") }
var showQuickScanner by remember { mutableStateOf(false) }
fun loadSais() { fun loadSais() {
scope.launch { scope.launch {
@@ -46,6 +50,13 @@ fun SaiListScreen(
loadSais() loadSais()
} }
val filteredSais = remember(sais, searchQuery) {
sais.filter {
it.ns.contains(searchQuery, ignoreCase = true) ||
(it.modelo?.contains(searchQuery, ignoreCase = true) ?: false)
}
}
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -63,19 +74,34 @@ fun SaiListScreen(
} }
} }
) { padding -> ) { padding ->
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
}
)
}
if (isLoading) { if (isLoading) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
CircularProgressIndicator() CircularProgressIndicator()
} }
} else { } else {
LazyColumn( LazyColumn(modifier = Modifier.weight(1f).fillMaxWidth()) {
modifier = Modifier items(filteredSais) { sai ->
.fillMaxSize()
.padding(padding)
) {
items(sais) { sai ->
ListItem( ListItem(
headlineContent = { Text("NS: ${sai.ns}") }, headlineContent = { Text("NS: ${sai.ns}") },
supportingContent = { Text("Modelo: ${sai.modelo ?: "N/A"}") },
trailingContent = { trailingContent = {
Row { Row {
IconButton(onClick = { onNavigateToEdit(sai.id) }) { IconButton(onClick = { onNavigateToEdit(sai.id) }) {
@@ -97,3 +123,4 @@ fun SaiListScreen(
} }
} }
} }
}
@@ -2,13 +2,10 @@ package com.example.saipp.ui.management
import android.Manifest import android.Manifest
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.* import androidx.compose.material3.*
@@ -16,9 +13,10 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat 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.data.SaiRepository
import com.example.saipp.ui.scanner.BarcodeScannerView import com.example.saipp.ui.scanner.BarcodeScannerView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -55,6 +53,7 @@ fun SaiScannerScreen(
} }
var scannedBarcode by remember { mutableStateOf<String?>(null) } var scannedBarcode by remember { mutableStateOf<String?>(null) }
var modelo by remember { mutableStateOf("") }
var isSaving by remember { mutableStateOf(false) } var isSaving by remember { mutableStateOf(false) }
Scaffold( Scaffold(
@@ -91,21 +90,49 @@ fun SaiScannerScreen(
} }
scannedBarcode?.let { barcode -> scannedBarcode?.let { barcode ->
ResultDialog( AlertDialog(
barcode = barcode, onDismissRequest = { scannedBarcode = null },
onDismiss = { scannedBarcode = null }, title = { Text(text = stringResource(R.string.dialog_title)) },
onConfirm = { 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 scannedBarcode = null
isSaving = true isSaving = true
scope.launch { scope.launch {
repository.insert(barcode) // 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 { .onSuccess {
Toast.makeText(context, "SAI guardado", Toast.LENGTH_SHORT).show() Toast.makeText(context, "SAI guardado", Toast.LENGTH_SHORT).show()
onNavigateBack() 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))
}
} }
) )
} }
+7 -3
View File
@@ -15,9 +15,13 @@
<string name="no_rows_inserted">No se insertaron filas en la base de datos</string> <string name="no_rows_inserted">No se insertaron filas en la base de datos</string>
<string name="label_sai">SAI</string> <string name="label_sai">SAI</string>
<string name="label_brand">Marca</string> <string name="label_brand">Marca</string>
<string name="label_install_date">Fecha Instalación (AAAA-MM-DD HH:MM:SS)</string> <string name="label_modelo">Modelo</string>
<string name="label_install_date">Fecha Instalación (DD-MM-AAAA)</string>
<string name="select_sai">Seleccionar SAI</string> <string name="select_sai">Seleccionar SAI</string>
<string name="no_sai">Ninguno</string> <string name="no_sai">Ninguno</string>
<string name="placeholder_search">Buscar...</string>
<string name="error_duplicate_sai">Este SAI ya está registrado en el sistema.</string>
<string name="scan_to_search">Escanear para buscar</string>
<string name="menu_sais">Gestión de SAIs</string> <string name="menu_sais">Gestión de SAIs</string>
<string name="menu_batteries">Gestión de Baterías</string> <string name="menu_batteries">Gestión de Baterías</string>
@@ -25,7 +29,7 @@
<string name="menu_assignments">Gestión de Asignaciones</string> <string name="menu_assignments">Gestión de Asignaciones</string>
<string name="label_person">Persona</string> <string name="label_person">Persona</string>
<string name="label_assigned_date">Fecha Asignación (AAAA-MM-DD)</string> <string name="label_assigned_date">Fecha Asignación (DD-MM-AAAA)</string>
<string name="label_return_date">Fecha Devolución (AAAA-MM-DD)</string> <string name="label_return_date">Fecha Devolución (DD-MM-AAAA)</string>
<string name="select_person">Seleccionar Persona</string> <string name="select_person">Seleccionar Persona</string>
</resources> </resources>