Aplicacion terminada

This commit is contained in:
2026-07-31 09:51:40 +02:00
parent 2fc7ad3f52
commit 2d185416c6
22 changed files with 1315 additions and 97 deletions
+18 -6
View File
@@ -43,17 +43,29 @@ CREATE TABLE SAIs (
); );
-- Table for Batteries -- Table for Batteries
CREATE TABLE baterias ( CREATE TABLE Bateria (
id_bateria INT AUTO_INCREMENT PRIMARY KEY, id_bateria INT AUTO_INCREMENT PRIMARY KEY,
modelo VARCHAR(255) NOT NULL, id_sai INT,
estado VARCHAR(255) NOT NULL marca VARCHAR(255),
fecha_instalacion DATETIME,
FOREIGN KEY (id_sai) REFERENCES SAIs(id_sai)
); );
-- Table for People -- Table for People
CREATE TABLE personas ( CREATE TABLE Persona (
id_persona INT AUTO_INCREMENT PRIMARY KEY, id_persona INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(255) NOT NULL, nombre VARCHAR(255) NOT NULL
puesto VARCHAR(255) NOT NULL );
-- Table for Assignments
CREATE TABLE Asignado (
id_asignacion INT AUTO_INCREMENT PRIMARY KEY,
id_sai INT,
id_persona INT,
fecha_asignado DATE,
fecha_devolucion DATE,
FOREIGN KEY (id_sai) REFERENCES SAIs(id_sai),
FOREIGN KEY (id_persona) REFERENCES Persona(id_persona)
); );
``` ```
@@ -0,0 +1,120 @@
package com.example.saipp.data
import com.example.saipp.data.model.Assignment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.sql.SQLException
import java.sql.Types
class AssignmentRepository {
suspend fun getAll(): Result<List<Assignment>> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try {
val sql = """
SELECT a.id_asignacion, a.id_sai, a.id_persona, a.fecha_asignado, a.fecha_devolucion,
s.ns as sai_ns, p.nombre as person_name
FROM Asignado a
LEFT JOIN SAIs s ON a.id_sai = s.id_sai
LEFT JOIN Persona p ON a.id_persona = p.id_persona
""".trimIndent()
val statement = connection.prepareStatement(sql)
val resultSet = statement.executeQuery()
val items = mutableListOf<Assignment>()
while (resultSet.next()) {
items.add(Assignment(
id = resultSet.getInt("id_asignacion"),
idSai = resultSet.getInt("id_sai"),
idPersona = resultSet.getInt("id_persona"),
assignedDate = resultSet.getString("fecha_asignado"),
returnDate = resultSet.getString("fecha_devolucion"),
saiNs = resultSet.getString("sai_ns"),
personName = resultSet.getString("person_name")
))
}
Result.success(items)
} catch (e: SQLException) {
Result.failure(e)
} finally {
connection.close()
}
}
suspend fun getById(id: Int): Result<Assignment> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try {
val sql = "SELECT id_asignacion, id_sai, id_persona, fecha_asignado, fecha_devolucion FROM Asignado WHERE id_asignacion = ?"
val statement = connection.prepareStatement(sql)
statement.setInt(1, id)
val resultSet = statement.executeQuery()
if (resultSet.next()) {
Result.success(Assignment(
id = resultSet.getInt("id_asignacion"),
idSai = resultSet.getInt("id_sai"),
idPersona = resultSet.getInt("id_persona"),
assignedDate = resultSet.getString("fecha_asignado"),
returnDate = resultSet.getString("fecha_devolucion")
))
} else {
Result.failure(Exception("not_found"))
}
} catch (e: SQLException) {
Result.failure(e)
} finally {
connection.close()
}
}
suspend fun insert(item: Assignment): Result<Unit> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try {
val sql = "INSERT INTO Asignado (id_sai, id_persona, fecha_asignado, fecha_devolucion) VALUES (?, ?, ?, ?)"
val statement = connection.prepareStatement(sql)
statement.setInt(1, item.idSai)
statement.setInt(2, item.idPersona)
statement.setString(3, item.assignedDate)
statement.setString(4, item.returnDate)
if (statement.executeUpdate() > 0) Result.success(Unit)
else Result.failure(Exception("no_rows_inserted"))
} catch (e: SQLException) {
Result.failure(e)
} finally {
connection.close()
}
}
suspend fun update(item: Assignment): Result<Unit> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try {
val sql = "UPDATE Asignado SET id_sai = ?, id_persona = ?, fecha_asignado = ?, fecha_devolucion = ? WHERE id_asignacion = ?"
val statement = connection.prepareStatement(sql)
statement.setInt(1, item.idSai)
statement.setInt(2, item.idPersona)
statement.setString(3, item.assignedDate)
statement.setString(4, item.returnDate)
statement.setInt(5, item.id)
if (statement.executeUpdate() > 0) Result.success(Unit)
else Result.failure(Exception("update_failed"))
} catch (e: SQLException) {
Result.failure(e)
} finally {
connection.close()
}
}
suspend fun delete(id: Int): Result<Unit> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try {
val sql = "DELETE FROM Asignado WHERE id_asignacion = ?"
val statement = connection.prepareStatement(sql)
statement.setInt(1, id)
if (statement.executeUpdate() > 0) Result.success(Unit)
else Result.failure(Exception("delete_failed"))
} catch (e: SQLException) {
Result.failure(e)
} finally {
connection.close()
}
}
}
@@ -4,20 +4,22 @@ import com.example.saipp.data.model.Battery
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.sql.SQLException import java.sql.SQLException
import java.sql.Types
class BatteryRepository { class BatteryRepository {
suspend fun getAll(): Result<List<Battery>> = withContext(Dispatchers.IO) { suspend fun getAll(): Result<List<Battery>> = 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_bateria, modelo, estado FROM baterias" val sql = "SELECT id_bateria, id_sai, marca, fecha_instalacion FROM Bateria"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
val resultSet = statement.executeQuery() val resultSet = statement.executeQuery()
val items = mutableListOf<Battery>() val items = mutableListOf<Battery>()
while (resultSet.next()) { while (resultSet.next()) {
items.add(Battery( items.add(Battery(
id = resultSet.getInt("id_bateria"), id = resultSet.getInt("id_bateria"),
model = resultSet.getString("modelo"), idSai = resultSet.getInt("id_sai").takeIf { !resultSet.wasNull() },
status = resultSet.getString("estado") brand = resultSet.getString("marca"),
installDate = resultSet.getString("fecha_instalacion")
)) ))
} }
Result.success(items) Result.success(items)
@@ -31,10 +33,12 @@ class BatteryRepository {
suspend fun insert(item: Battery): Result<Unit> = withContext(Dispatchers.IO) { suspend fun insert(item: Battery): 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 = "INSERT INTO baterias (modelo, estado) VALUES (?, ?)" val sql = "INSERT INTO Bateria (id_sai, marca, fecha_instalacion) VALUES (?, ?, ?)"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
statement.setString(1, item.model) if (item.idSai != null) statement.setInt(1, item.idSai) else statement.setNull(1, Types.INTEGER)
statement.setString(2, item.status) statement.setString(2, item.brand)
statement.setString(3, item.installDate)
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,11 +51,13 @@ class BatteryRepository {
suspend fun update(item: Battery): Result<Unit> = withContext(Dispatchers.IO) { suspend fun update(item: Battery): 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 baterias SET modelo = ?, estado = ? WHERE id_bateria = ?" val sql = "UPDATE Bateria SET id_sai = ?, marca = ?, fecha_instalacion = ? WHERE id_bateria = ?"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
statement.setString(1, item.model) if (item.idSai != null) statement.setInt(1, item.idSai) else statement.setNull(1, Types.INTEGER)
statement.setString(2, item.status) statement.setString(2, item.brand)
statement.setInt(3, item.id) statement.setString(3, item.installDate)
statement.setInt(4, item.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) {
@@ -64,7 +70,7 @@ class BatteryRepository {
suspend fun delete(id: Int): Result<Unit> = withContext(Dispatchers.IO) { suspend fun delete(id: Int): 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 = "DELETE FROM baterias WHERE id_bateria = ?" val sql = "DELETE FROM Bateria WHERE id_bateria = ?"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
statement.setInt(1, id) statement.setInt(1, id)
if (statement.executeUpdate() > 0) Result.success(Unit) if (statement.executeUpdate() > 0) Result.success(Unit)
@@ -75,4 +81,28 @@ class BatteryRepository {
connection.close() connection.close()
} }
} }
suspend fun getById(id: Int): Result<Battery> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try {
val sql = "SELECT id_bateria, id_sai, marca, fecha_instalacion FROM Bateria WHERE id_bateria = ?"
val statement = connection.prepareStatement(sql)
statement.setInt(1, id)
val resultSet = statement.executeQuery()
if (resultSet.next()) {
Result.success(Battery(
id = resultSet.getInt("id_bateria"),
idSai = resultSet.getInt("id_sai").takeIf { !resultSet.wasNull() },
brand = resultSet.getString("marca"),
installDate = resultSet.getString("fecha_instalacion")
))
} else {
Result.failure(Exception("not_found"))
}
} catch (e: SQLException) {
Result.failure(e)
} finally {
connection.close()
}
}
} }
@@ -9,15 +9,14 @@ class PersonRepository {
suspend fun getAll(): Result<List<Person>> = withContext(Dispatchers.IO) { suspend fun getAll(): Result<List<Person>> = 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_persona, nombre, puesto FROM personas" val sql = "SELECT id_persona, nombre FROM Persona"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
val resultSet = statement.executeQuery() val resultSet = statement.executeQuery()
val items = mutableListOf<Person>() val items = mutableListOf<Person>()
while (resultSet.next()) { while (resultSet.next()) {
items.add(Person( items.add(Person(
id = resultSet.getInt("id_persona"), id = resultSet.getInt("id_persona"),
name = resultSet.getString("nombre"), name = resultSet.getString("nombre")
position = resultSet.getString("puesto")
)) ))
} }
Result.success(items) Result.success(items)
@@ -31,10 +30,9 @@ class PersonRepository {
suspend fun insert(item: Person): Result<Unit> = withContext(Dispatchers.IO) { suspend fun insert(item: Person): 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 = "INSERT INTO personas (nombre, puesto) VALUES (?, ?)" val sql = "INSERT INTO Persona (nombre) VALUES (?)"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
statement.setString(1, item.name) statement.setString(1, item.name)
statement.setString(2, item.position)
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,11 +45,10 @@ class PersonRepository {
suspend fun update(item: Person): Result<Unit> = withContext(Dispatchers.IO) { suspend fun update(item: Person): 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 personas SET nombre = ?, puesto = ? WHERE id_persona = ?" val sql = "UPDATE Persona SET nombre = ? WHERE id_persona = ?"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
statement.setString(1, item.name) statement.setString(1, item.name)
statement.setString(2, item.position) statement.setInt(2, item.id)
statement.setInt(3, item.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) {
@@ -64,7 +61,7 @@ class PersonRepository {
suspend fun delete(id: Int): Result<Unit> = withContext(Dispatchers.IO) { suspend fun delete(id: Int): 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 = "DELETE FROM personas WHERE id_persona = ?" val sql = "DELETE FROM Persona WHERE id_persona = ?"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
statement.setInt(1, id) statement.setInt(1, id)
if (statement.executeUpdate() > 0) Result.success(Unit) if (statement.executeUpdate() > 0) Result.success(Unit)
@@ -75,4 +72,26 @@ class PersonRepository {
connection.close() connection.close()
} }
} }
suspend fun getById(id: Int): Result<Person> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
try {
val sql = "SELECT id_persona, nombre FROM Persona WHERE id_persona = ?"
val statement = connection.prepareStatement(sql)
statement.setInt(1, id)
val resultSet = statement.executeQuery()
if (resultSet.next()) {
Result.success(Person(
id = resultSet.getInt("id_persona"),
name = resultSet.getString("nombre")
))
} else {
Result.failure(Exception("not_found"))
}
} catch (e: SQLException) {
Result.failure(e)
} finally {
connection.close()
}
}
} }
@@ -74,4 +74,23 @@ class SaiRepository {
connection.close() connection.close()
} }
} }
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 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")))
} else {
Result.failure(Exception("not_found"))
}
} catch (e: SQLException) {
Result.failure(e)
} finally {
connection.close()
}
}
} }
@@ -0,0 +1,12 @@
package com.example.saipp.data.model
data class Assignment(
val id: Int = 0,
val idSai: Int,
val idPersona: Int,
val assignedDate: String? = null,
val returnDate: String? = null,
// Optional display fields for joining data
val saiNs: String? = null,
val personName: String? = null
)
@@ -2,6 +2,7 @@ package com.example.saipp.data.model
data class Battery( data class Battery(
val id: Int = 0, val id: Int = 0,
val model: String, val idSai: Int? = null,
val status: String val brand: String? = null,
val installDate: String? = null
) )
@@ -2,6 +2,5 @@ package com.example.saipp.data.model
data class Person( data class Person(
val id: Int = 0, val id: Int = 0,
val name: String, val name: String
val position: String
) )
@@ -1,19 +1,35 @@
package com.example.saipp.ui package com.example.saipp.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.saipp.ui.management.* import com.example.saipp.ui.management.*
sealed class Screen(val route: String) { sealed class Screen(val route: String) {
object Home : Screen("home") object Home : Screen("home")
object SaiList : Screen("sai_list") object SaiList : Screen("sai_list")
object SaiScanner : Screen("sai_scanner") object SaiScanner : Screen("sai_scanner")
object EditSai : Screen("edit_sai/{saiId}") {
fun createRoute(id: Int) = "edit_sai/$id"
}
object BatteryList : Screen("battery_list") object BatteryList : Screen("battery_list")
object AddBattery : Screen("add_battery") object AddBattery : Screen("add_battery")
object EditBattery : Screen("edit_battery/{batteryId}") {
fun createRoute(id: Int) = "edit_battery/$id"
}
object PersonList : Screen("person_list") object PersonList : Screen("person_list")
object AddPerson : Screen("add_person") object AddPerson : Screen("add_person")
object EditPerson : Screen("edit_person/{personId}") {
fun createRoute(id: Int) = "edit_person/$id"
}
object AssignmentList : Screen("assignment_list")
object AddAssignment : Screen("add_assignment")
object EditAssignment : Screen("edit_assignment/{assignmentId}") {
fun createRoute(id: Int) = "edit_assignment/$id"
}
} }
@Composable @Composable
@@ -25,13 +41,17 @@ fun AppNavigation() {
HomeScreen( HomeScreen(
onNavigateToSais = { navController.navigate(Screen.SaiList.route) }, onNavigateToSais = { navController.navigate(Screen.SaiList.route) },
onNavigateToBatteries = { navController.navigate(Screen.BatteryList.route) }, onNavigateToBatteries = { navController.navigate(Screen.BatteryList.route) },
onNavigateToPeople = { navController.navigate(Screen.PersonList.route) } onNavigateToPeople = { navController.navigate(Screen.PersonList.route) },
onNavigateToAssignments = { navController.navigate(Screen.AssignmentList.route) }
) )
} }
// SAIs
composable(Screen.SaiList.route) { composable(Screen.SaiList.route) {
SaiListScreen( SaiListScreen(
onNavigateBack = { navController.popBackStack() }, onNavigateBack = { navController.popBackStack() },
onNavigateToScan = { navController.navigate(Screen.SaiScanner.route) } onNavigateToScan = { navController.navigate(Screen.SaiScanner.route) },
onNavigateToEdit = { id -> navController.navigate(Screen.EditSai.createRoute(id)) }
) )
} }
composable(Screen.SaiScanner.route) { composable(Screen.SaiScanner.route) {
@@ -39,27 +59,69 @@ fun AppNavigation() {
onNavigateBack = { navController.popBackStack() } onNavigateBack = { navController.popBackStack() }
) )
} }
composable(
route = Screen.EditSai.route,
arguments = listOf(navArgument("saiId") { type = NavType.IntType })
) { backStackEntry ->
val id = backStackEntry.arguments?.getInt("saiId") ?: 0
EditSaiScreen(saiId = id, onNavigateBack = { navController.popBackStack() })
}
// Batteries
composable(Screen.BatteryList.route) { composable(Screen.BatteryList.route) {
BatteryListScreen( BatteryListScreen(
onNavigateBack = { navController.popBackStack() }, onNavigateBack = { navController.popBackStack() },
onNavigateToAdd = { navController.navigate(Screen.AddBattery.route) } onNavigateToAdd = { navController.navigate(Screen.AddBattery.route) },
onNavigateToEdit = { id -> navController.navigate(Screen.EditBattery.createRoute(id)) }
) )
} }
composable(Screen.AddBattery.route) { composable(Screen.AddBattery.route) {
AddBatteryScreen( AddBatteryScreen(onNavigateBack = { navController.popBackStack() })
onNavigateBack = { navController.popBackStack() }
)
} }
composable(
route = Screen.EditBattery.route,
arguments = listOf(navArgument("batteryId") { type = NavType.IntType })
) { backStackEntry ->
val id = backStackEntry.arguments?.getInt("batteryId") ?: 0
EditBatteryScreen(batteryId = id, onNavigateBack = { navController.popBackStack() })
}
// People
composable(Screen.PersonList.route) { composable(Screen.PersonList.route) {
PersonListScreen( PersonListScreen(
onNavigateBack = { navController.popBackStack() }, onNavigateBack = { navController.popBackStack() },
onNavigateToAdd = { navController.navigate(Screen.AddPerson.route) } onNavigateToAdd = { navController.navigate(Screen.AddPerson.route) },
onNavigateToEdit = { id -> navController.navigate(Screen.EditPerson.createRoute(id)) }
) )
} }
composable(Screen.AddPerson.route) { composable(Screen.AddPerson.route) {
AddPersonScreen( AddPersonScreen(onNavigateBack = { navController.popBackStack() })
onNavigateBack = { navController.popBackStack() } }
composable(
route = Screen.EditPerson.route,
arguments = listOf(navArgument("personId") { type = NavType.IntType })
) { backStackEntry ->
val id = backStackEntry.arguments?.getInt("personId") ?: 0
EditPersonScreen(personId = id, onNavigateBack = { navController.popBackStack() })
}
// Assignments
composable(Screen.AssignmentList.route) {
AssignmentListScreen(
onNavigateBack = { navController.popBackStack() },
onNavigateToAdd = { navController.navigate(Screen.AddAssignment.route) },
onNavigateToEdit = { id -> navController.navigate(Screen.EditAssignment.createRoute(id)) }
) )
} }
composable(Screen.AddAssignment.route) {
AddAssignmentScreen(onNavigateBack = { navController.popBackStack() })
}
composable(
route = Screen.EditAssignment.route,
arguments = listOf(navArgument("assignmentId") { type = NavType.IntType })
) { backStackEntry ->
val id = backStackEntry.arguments?.getInt("assignmentId") ?: 0
EditAssignmentScreen(assignmentId = id, onNavigateBack = { navController.popBackStack() })
}
} }
} }
@@ -0,0 +1,179 @@
package com.example.saipp.ui.management
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
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.AssignmentRepository
import com.example.saipp.data.PersonRepository
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 kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddAssignmentScreen(
onNavigateBack: () -> Unit
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val repository = remember { AssignmentRepository() }
val saiRepository = remember { SaiRepository() }
val personRepository = remember { PersonRepository() }
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 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) }
LaunchedEffect(Unit) {
saiRepository.getAll().onSuccess { sais = it }
personRepository.getAll().onSuccess { people = it }
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("Añadir Asignación") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
}
}
)
}
) { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
) {
// SAI Selection
ExposedDropdownMenuBox(
expanded = saiExpanded,
onExpandedChange = { saiExpanded = !saiExpanded }
) {
val selectedSai = sais.find { it.id == selectedSaiId }
OutlinedTextField(
value = selectedSai?.ns ?: stringResource(R.string.select_sai),
onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_sai)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = saiExpanded) },
modifier = Modifier.menuAnchor().fillMaxWidth()
)
ExposedDropdownMenu(
expanded = saiExpanded,
onDismissRequest = { saiExpanded = false }
) {
sais.forEach { sai ->
DropdownMenuItem(
text = { Text(sai.ns) },
onClick = {
selectedSaiId = sai.id
saiExpanded = false
}
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// Person Selection
ExposedDropdownMenuBox(
expanded = personExpanded,
onExpandedChange = { personExpanded = !personExpanded }
) {
val selectedPerson = people.find { it.id == selectedPersonId }
OutlinedTextField(
value = selectedPerson?.name ?: stringResource(R.string.select_person),
onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_person)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = personExpanded) },
modifier = Modifier.menuAnchor().fillMaxWidth()
)
ExposedDropdownMenu(
expanded = personExpanded,
onDismissRequest = { personExpanded = false }
) {
people.forEach { person ->
DropdownMenuItem(
text = { Text(person.name) },
onClick = {
selectedPersonId = person.id
personExpanded = false
}
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = assignedDate,
onValueChange = { assignedDate = it },
label = { Text(stringResource(R.string.label_assigned_date)) },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = returnDate,
onValueChange = { returnDate = it },
label = { Text(stringResource(R.string.label_return_date)) },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = {
if (selectedSaiId != null && selectedPersonId != null) {
isSaving = true
scope.launch {
repository.insert(
Assignment(
idSai = selectedSaiId!!,
idPersona = selectedPersonId!!,
assignedDate = assignedDate.takeIf { it.isNotBlank() },
returnDate = returnDate.takeIf { it.isNotBlank() }
)
).onSuccess {
Toast.makeText(context, "Asignación guardada", Toast.LENGTH_SHORT).show()
onNavigateBack()
}.onFailure {
Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show()
}
isSaving = false
}
}
},
modifier = Modifier.fillMaxWidth(),
enabled = !isSaving && selectedSaiId != null && selectedPersonId != null
) {
if (isSaving) CircularProgressIndicator(modifier = Modifier.size(24.dp))
else Text("Guardar")
}
}
}
}
@@ -8,9 +8,13 @@ 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.BatteryRepository 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.Battery
import com.example.saipp.data.model.Sai
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -20,11 +24,20 @@ fun AddBatteryScreen(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val repository = remember { BatteryRepository() } val batteryRepository = remember { BatteryRepository() }
val saiRepository = remember { SaiRepository() }
var model by remember { mutableStateOf("") } var brand by remember { mutableStateOf("") }
var status by remember { mutableStateOf("") } var installDate by remember { mutableStateOf("") }
var selectedSaiId by remember { mutableStateOf<Int?>(null) }
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) }
LaunchedEffect(Unit) {
saiRepository.getAll().onSuccess { sais = it }
}
Scaffold( Scaffold(
topBar = { topBar = {
@@ -44,33 +57,80 @@ fun AddBatteryScreen(
.padding(padding) .padding(padding)
.padding(16.dp) .padding(16.dp)
) { ) {
OutlinedTextField( // SAI Selection
value = model, ExposedDropdownMenuBox(
onValueChange = { model = it }, expanded = expanded,
label = { Text("Modelo") }, onExpandedChange = { expanded = !expanded }
modifier = Modifier.fillMaxWidth() ) {
) val selectedSai = sais.find { it.id == selectedSaiId }
OutlinedTextField(
value = selectedSai?.ns ?: stringResource(R.string.no_sai),
onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_sai)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier.menuAnchor().fillMaxWidth()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.no_sai)) },
onClick = {
selectedSaiId = null
expanded = false
}
)
sais.forEach { sai ->
DropdownMenuItem(
text = { Text(sai.ns) },
onClick = {
selectedSaiId = sai.id
expanded = false
}
)
}
}
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField( OutlinedTextField(
value = status, value = brand,
onValueChange = { status = it }, onValueChange = { brand = it },
label = { Text("Estado") }, label = { Text(stringResource(R.string.label_brand)) },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = installDate,
onValueChange = { installDate = it },
label = { Text(stringResource(R.string.label_install_date)) },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
Button( Button(
onClick = { onClick = {
if (model.isNotBlank() && status.isNotBlank()) { isSaving = true
isSaving = true scope.launch {
scope.launch { batteryRepository.insert(
repository.insert(Battery(model = model, status = status)) Battery(
.onSuccess { idSai = selectedSaiId,
Toast.makeText(context, "Batería añadida", Toast.LENGTH_SHORT).show() brand = brand,
onNavigateBack() installDate = installDate.takeIf { it.isNotBlank() }
} )
.onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() } ).onSuccess {
isSaving = false Toast.makeText(context, "Batería añadida", Toast.LENGTH_SHORT).show()
onNavigateBack()
}.onFailure {
Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show()
} }
isSaving = false
} }
}, },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -23,7 +23,6 @@ fun AddPersonScreen(
val repository = remember { PersonRepository() } val repository = remember { PersonRepository() }
var name by remember { mutableStateOf("") } var name by remember { mutableStateOf("") }
var position by remember { mutableStateOf("") }
var isSaving by remember { mutableStateOf(false) } var isSaving by remember { mutableStateOf(false) }
Scaffold( Scaffold(
@@ -50,20 +49,13 @@ fun AddPersonScreen(
label = { Text("Nombre") }, label = { Text("Nombre") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = position,
onValueChange = { position = it },
label = { Text("Puesto") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
Button( Button(
onClick = { onClick = {
if (name.isNotBlank() && position.isNotBlank()) { if (name.isNotBlank()) {
isSaving = true isSaving = true
scope.launch { scope.launch {
repository.insert(Person(name = name, position = position)) repository.insert(Person(name = name))
.onSuccess { .onSuccess {
Toast.makeText(context, "Persona añadida", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Persona añadida", Toast.LENGTH_SHORT).show()
onNavigateBack() onNavigateBack()
@@ -0,0 +1,108 @@
package com.example.saipp.ui.management
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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 kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AssignmentListScreen(
onNavigateBack: () -> Unit,
onNavigateToAdd: () -> Unit,
onNavigateToEdit: (Int) -> Unit
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val repository = remember { AssignmentRepository() }
var items by remember { mutableStateOf<List<Assignment>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) }
fun loadItems() {
scope.launch {
isLoading = true
repository.getAll()
.onSuccess { items = it }
.onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() }
isLoading = false
}
}
LaunchedEffect(Unit) {
loadItems()
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("Listado de Asignaciones") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
}
}
)
},
floatingActionButton = {
FloatingActionButton(onClick = onNavigateToAdd) {
Icon(Icons.Default.Add, contentDescription = "Añadir Asignación")
}
}
) { 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.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}")
}
}
},
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")
}
}
}
)
HorizontalDivider()
}
}
}
}
}
@@ -8,6 +8,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -22,7 +23,8 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun BatteryListScreen( fun BatteryListScreen(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
onNavigateToAdd: () -> Unit onNavigateToAdd: () -> Unit,
onNavigateToEdit: (Int) -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -73,15 +75,25 @@ fun BatteryListScreen(
) { ) {
items(items) { item -> items(items) { item ->
ListItem( ListItem(
headlineContent = { Text("Modelo: ${item.model}") }, headlineContent = { Text("Marca: ${item.brand ?: "Desconocida"}") },
supportingContent = { Text("Estado: ${item.status}") }, supportingContent = {
Column {
Text("Instalación: ${item.installDate ?: "N/A"}")
item.idSai?.let { Text("SAI ID: $it") }
}
},
trailingContent = { trailingContent = {
IconButton(onClick = { Row {
scope.launch { IconButton(onClick = { onNavigateToEdit(item.id) }) {
repository.delete(item.id).onSuccess { loadItems() } 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")
} }
} }
) )
@@ -0,0 +1,203 @@
package com.example.saipp.ui.management
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
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.AssignmentRepository
import com.example.saipp.data.PersonRepository
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 kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditAssignmentScreen(
assignmentId: Int,
onNavigateBack: () -> Unit
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val repository = remember { AssignmentRepository() }
val saiRepository = remember { SaiRepository() }
val personRepository = remember { PersonRepository() }
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 sais by remember { mutableStateOf<List<Sai>>(emptyList()) }
var people by remember { mutableStateOf<List<Person>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) }
var isSaving by remember { mutableStateOf(false) }
var saiExpanded by remember { mutableStateOf(false) }
var personExpanded by remember { mutableStateOf(false) }
LaunchedEffect(assignmentId) {
val assignmentResult = repository.getById(assignmentId)
val saisResult = saiRepository.getAll()
val peopleResult = personRepository.getAll()
if (assignmentResult.isSuccess) {
val item = assignmentResult.getOrNull()!!
selectedSaiId = item.idSai
selectedPersonId = item.idPersona
assignedDate = item.assignedDate ?: ""
returnDate = item.returnDate ?: ""
sais = saisResult.getOrDefault(emptyList())
people = peopleResult.getOrDefault(emptyList())
isLoading = false
} else {
Toast.makeText(context, "Error al cargar asignación", Toast.LENGTH_SHORT).show()
onNavigateBack()
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("Editar Asignación") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
}
}
)
}
) { padding ->
if (isLoading) {
Box(modifier = Modifier.fillMaxSize().padding(padding), contentAlignment = androidx.compose.ui.Alignment.Center) {
CircularProgressIndicator()
}
} else {
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
) {
// SAI Selection
ExposedDropdownMenuBox(
expanded = saiExpanded,
onExpandedChange = { saiExpanded = !saiExpanded }
) {
val selectedSai = sais.find { it.id == selectedSaiId }
OutlinedTextField(
value = selectedSai?.ns ?: stringResource(R.string.select_sai),
onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_sai)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = saiExpanded) },
modifier = Modifier.menuAnchor().fillMaxWidth()
)
ExposedDropdownMenu(
expanded = saiExpanded,
onDismissRequest = { saiExpanded = false }
) {
sais.forEach { sai ->
DropdownMenuItem(
text = { Text(sai.ns) },
onClick = {
selectedSaiId = sai.id
saiExpanded = false
}
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// Person Selection
ExposedDropdownMenuBox(
expanded = personExpanded,
onExpandedChange = { personExpanded = !personExpanded }
) {
val selectedPerson = people.find { it.id == selectedPersonId }
OutlinedTextField(
value = selectedPerson?.name ?: stringResource(R.string.select_person),
onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_person)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = personExpanded) },
modifier = Modifier.menuAnchor().fillMaxWidth()
)
ExposedDropdownMenu(
expanded = personExpanded,
onDismissRequest = { personExpanded = false }
) {
people.forEach { person ->
DropdownMenuItem(
text = { Text(person.name) },
onClick = {
selectedPersonId = person.id
personExpanded = false
}
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = assignedDate,
onValueChange = { assignedDate = it },
label = { Text(stringResource(R.string.label_assigned_date)) },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = returnDate,
onValueChange = { returnDate = it },
label = { Text(stringResource(R.string.label_return_date)) },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = {
if (selectedSaiId != null && selectedPersonId != null) {
isSaving = true
scope.launch {
repository.update(
Assignment(
id = assignmentId,
idSai = selectedSaiId!!,
idPersona = selectedPersonId!!,
assignedDate = assignedDate.takeIf { it.isNotBlank() },
returnDate = returnDate.takeIf { it.isNotBlank() }
)
).onSuccess {
Toast.makeText(context, "Asignación actualizada", Toast.LENGTH_SHORT).show()
onNavigateBack()
}.onFailure {
Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show()
}
isSaving = false
}
}
},
modifier = Modifier.fillMaxWidth(),
enabled = !isSaving && selectedSaiId != null && selectedPersonId != null
) {
if (isSaving) CircularProgressIndicator(modifier = Modifier.size(24.dp))
else Text("Guardar Cambios")
}
}
}
}
}
@@ -0,0 +1,166 @@
package com.example.saipp.ui.management
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
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.BatteryRepository
import com.example.saipp.data.SaiRepository
import com.example.saipp.data.model.Battery
import com.example.saipp.data.model.Sai
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditBatteryScreen(
batteryId: Int,
onNavigateBack: () -> Unit
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val batteryRepository = remember { BatteryRepository() }
val saiRepository = remember { SaiRepository() }
var brand by remember { mutableStateOf("") }
var installDate 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) }
LaunchedEffect(batteryId) {
val batteryResult = batteryRepository.getById(batteryId)
val saisResult = saiRepository.getAll()
if (batteryResult.isSuccess) {
val battery = batteryResult.getOrNull()!!
brand = battery.brand ?: ""
installDate = battery.installDate ?: ""
selectedSaiId = battery.idSai
sais = saisResult.getOrDefault(emptyList())
isLoading = false
} else {
Toast.makeText(context, "Error al cargar batería", Toast.LENGTH_SHORT).show()
onNavigateBack()
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("Editar Batería") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
}
}
)
}
) { padding ->
if (isLoading) {
Box(modifier = Modifier.fillMaxSize().padding(padding), contentAlignment = androidx.compose.ui.Alignment.Center) {
CircularProgressIndicator()
}
} else {
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
) {
// SAI Selection
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded }
) {
val selectedSai = sais.find { it.id == selectedSaiId }
OutlinedTextField(
value = selectedSai?.ns ?: stringResource(R.string.no_sai),
onValueChange = {},
readOnly = true,
label = { Text(stringResource(R.string.label_sai)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier.menuAnchor().fillMaxWidth()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.no_sai)) },
onClick = {
selectedSaiId = null
expanded = false
}
)
sais.forEach { sai ->
DropdownMenuItem(
text = { Text(sai.ns) },
onClick = {
selectedSaiId = sai.id
expanded = false
}
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = brand,
onValueChange = { brand = it },
label = { Text(stringResource(R.string.label_brand)) },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = installDate,
onValueChange = { installDate = it },
label = { Text(stringResource(R.string.label_install_date)) },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = {
isSaving = true
scope.launch {
batteryRepository.update(
Battery(
id = batteryId,
idSai = selectedSaiId,
brand = brand,
installDate = installDate.takeIf { it.isNotBlank() }
)
).onSuccess {
Toast.makeText(context, "Batería actualizada", Toast.LENGTH_SHORT).show()
onNavigateBack()
}.onFailure {
Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show()
}
isSaving = false
}
},
modifier = Modifier.fillMaxWidth(),
enabled = !isSaving
) {
if (isSaving) CircularProgressIndicator(modifier = Modifier.size(24.dp))
else Text("Guardar Cambios")
}
}
}
}
}
@@ -0,0 +1,94 @@
package com.example.saipp.ui.management
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
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 kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditPersonScreen(
personId: Int,
onNavigateBack: () -> Unit
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val repository = remember { PersonRepository() }
var name by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(true) }
var isSaving by remember { mutableStateOf(false) }
LaunchedEffect(personId) {
repository.getById(personId).onSuccess {
name = it.name
isLoading = false
}.onFailure {
Toast.makeText(context, "Error al cargar persona", Toast.LENGTH_SHORT).show()
onNavigateBack()
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("Editar Persona") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
}
}
)
}
) { padding ->
if (isLoading) {
Box(modifier = Modifier.fillMaxSize().padding(padding), contentAlignment = androidx.compose.ui.Alignment.Center) {
CircularProgressIndicator()
}
} else {
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
) {
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Nombre") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = {
if (name.isNotBlank()) {
isSaving = true
scope.launch {
repository.update(Person(id = personId, name = name))
.onSuccess {
Toast.makeText(context, "Persona actualizada", Toast.LENGTH_SHORT).show()
onNavigateBack()
}
.onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() }
isSaving = false
}
}
},
modifier = Modifier.fillMaxWidth(),
enabled = !isSaving
) {
if (isSaving) CircularProgressIndicator(modifier = Modifier.size(24.dp))
else Text("Guardar Cambios")
}
}
}
}
}
@@ -0,0 +1,94 @@
package com.example.saipp.ui.management
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
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 kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EditSaiScreen(
saiId: Int,
onNavigateBack: () -> Unit
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val repository = remember { SaiRepository() }
var ns by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(true) }
var isSaving by remember { mutableStateOf(false) }
LaunchedEffect(saiId) {
repository.getById(saiId).onSuccess {
ns = it.ns
isLoading = false
}.onFailure {
Toast.makeText(context, "Error al cargar SAI", Toast.LENGTH_SHORT).show()
onNavigateBack()
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("Editar SAI") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
}
}
)
}
) { padding ->
if (isLoading) {
Box(modifier = Modifier.fillMaxSize().padding(padding), contentAlignment = androidx.compose.ui.Alignment.Center) {
CircularProgressIndicator()
}
} else {
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
) {
OutlinedTextField(
value = ns,
onValueChange = { ns = it },
label = { Text("Número de Serie") },
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))
.onSuccess {
Toast.makeText(context, "SAI actualizado", Toast.LENGTH_SHORT).show()
onNavigateBack()
}
.onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() }
isSaving = false
}
}
},
modifier = Modifier.fillMaxWidth(),
enabled = !isSaving
) {
if (isSaving) CircularProgressIndicator(modifier = Modifier.size(24.dp))
else Text("Guardar Cambios")
}
}
}
}
}
@@ -5,7 +5,6 @@ import androidx.compose.material3.*
import androidx.compose.runtime.* 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.graphics.vector.ImageVector
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 com.example.saipp.ui.ConnectionIndicator import com.example.saipp.ui.ConnectionIndicator
@@ -17,7 +16,8 @@ import com.example.saipp.data.MariaDbConnection
fun HomeScreen( fun HomeScreen(
onNavigateToSais: () -> Unit, onNavigateToSais: () -> Unit,
onNavigateToBatteries: () -> Unit, onNavigateToBatteries: () -> Unit,
onNavigateToPeople: () -> Unit onNavigateToPeople: () -> Unit,
onNavigateToAssignments: () -> Unit
) { ) {
var connectionStatus by remember { mutableStateOf<Result<Unit>?>(null) } var connectionStatus by remember { mutableStateOf<Result<Unit>?>(null) }
var isCheckingConnection by remember { mutableStateOf(true) } var isCheckingConnection by remember { mutableStateOf(true) }
@@ -49,7 +49,7 @@ fun HomeScreen(
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
MenuButton( MenuButton(
text = "Gestión de SAIs", text = stringResource(R.string.menu_sais),
onClick = onNavigateToSais, onClick = onNavigateToSais,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -57,7 +57,7 @@ fun HomeScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
MenuButton( MenuButton(
text = "Gestión de Baterías", text = stringResource(R.string.menu_batteries),
onClick = onNavigateToBatteries, onClick = onNavigateToBatteries,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -65,10 +65,18 @@ fun HomeScreen(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
MenuButton( MenuButton(
text = "Gestión de Personas", text = stringResource(R.string.menu_people),
onClick = onNavigateToPeople, onClick = onNavigateToPeople,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(16.dp))
MenuButton(
text = stringResource(R.string.menu_assignments),
onClick = onNavigateToAssignments,
modifier = Modifier.fillMaxWidth()
)
} }
} }
} }
@@ -8,6 +8,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -22,7 +23,8 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun PersonListScreen( fun PersonListScreen(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
onNavigateToAdd: () -> Unit onNavigateToAdd: () -> Unit,
onNavigateToEdit: (Int) -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -74,14 +76,18 @@ fun PersonListScreen(
items(items) { item -> items(items) { item ->
ListItem( ListItem(
headlineContent = { Text(item.name) }, headlineContent = { Text(item.name) },
supportingContent = { Text(item.position) },
trailingContent = { trailingContent = {
IconButton(onClick = { Row {
scope.launch { IconButton(onClick = { onNavigateToEdit(item.id) }) {
repository.delete(item.id).onSuccess { loadItems() } 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")
} }
} }
) )
@@ -8,6 +8,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -22,7 +23,8 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun SaiListScreen( fun SaiListScreen(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
onNavigateToScan: () -> Unit onNavigateToScan: () -> Unit,
onNavigateToEdit: (Int) -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -75,12 +77,17 @@ fun SaiListScreen(
ListItem( ListItem(
headlineContent = { Text("NS: ${sai.ns}") }, headlineContent = { Text("NS: ${sai.ns}") },
trailingContent = { trailingContent = {
IconButton(onClick = { Row {
scope.launch { IconButton(onClick = { onNavigateToEdit(sai.id) }) {
repository.delete(sai.id).onSuccess { loadSais() } 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")
} }
} }
) )
+15
View File
@@ -13,4 +13,19 @@
<string name="db_connection_failed">No se pudo establecer conexión con el servidor</string> <string name="db_connection_failed">No se pudo establecer conexión con el servidor</string>
<string name="db_connection_error">Error de conexión a la base de datos</string> <string name="db_connection_error">Error de conexión a la base de datos</string>
<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_brand">Marca</string>
<string name="label_install_date">Fecha Instalación (AAAA-MM-DD HH:MM:SS)</string>
<string name="select_sai">Seleccionar SAI</string>
<string name="no_sai">Ninguno</string>
<string name="menu_sais">Gestión de SAIs</string>
<string name="menu_batteries">Gestión de Baterías</string>
<string name="menu_people">Gestión de Personas</string>
<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="select_person">Seleccionar Persona</string>
</resources> </resources>