Aplicacion terminada
This commit is contained in:
@@ -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<List<Sai>> = 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<Sai>()
|
||||
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<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"))
|
||||
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<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)
|
||||
else Result.failure(Exception("no_rows_inserted"))
|
||||
} catch (e: SQLException) {
|
||||
@@ -47,10 +66,11 @@ class SaiRepository {
|
||||
suspend fun update(sai: Sai): Result<Unit> = 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<Sai> = 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"))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<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 people by remember { mutableStateOf<List<Person>>(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()
|
||||
|
||||
@@ -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<Int?>(null) }
|
||||
|
||||
var sais by remember { mutableStateOf<List<Sai>>(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()
|
||||
|
||||
@@ -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<List<Assignment>>(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<Battery>>(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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 people by remember { mutableStateOf<List<Person>>(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()
|
||||
|
||||
@@ -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<Int?>(null) }
|
||||
|
||||
var sais by remember { mutableStateOf<List<Sai>>(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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<List<Person>>(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<Sai>>(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String?>(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))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
<string name="no_rows_inserted">No se insertaron filas en la base de datos</string>
|
||||
<string name="label_sai">SAI</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="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_batteries">Gestión de Baterías</string>
|
||||
@@ -25,7 +29,7 @@
|
||||
<string name="menu_assignments">Gestión de Asignaciones</string>
|
||||
|
||||
<string name="label_person">Persona</string>
|
||||
<string name="label_assigned_date">Fecha Asignación (AAAA-MM-DD)</string>
|
||||
<string name="label_return_date">Fecha Devolució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 (DD-MM-AAAA)</string>
|
||||
<string name="select_person">Seleccionar Persona</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user