diff --git a/README.md b/README.md index 5dda6bd..64f32fe 100644 --- a/README.md +++ b/README.md @@ -43,17 +43,29 @@ CREATE TABLE SAIs ( ); -- Table for Batteries -CREATE TABLE baterias ( +CREATE TABLE Bateria ( id_bateria INT AUTO_INCREMENT PRIMARY KEY, - modelo VARCHAR(255) NOT NULL, - estado VARCHAR(255) NOT NULL + id_sai INT, + marca VARCHAR(255), + fecha_instalacion DATETIME, + FOREIGN KEY (id_sai) REFERENCES SAIs(id_sai) ); -- Table for People -CREATE TABLE personas ( +CREATE TABLE Persona ( id_persona INT AUTO_INCREMENT PRIMARY KEY, - nombre VARCHAR(255) NOT NULL, - puesto VARCHAR(255) NOT NULL + nombre 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) ); ``` diff --git a/app/src/main/java/com/example/saipp/data/AssignmentRepository.kt b/app/src/main/java/com/example/saipp/data/AssignmentRepository.kt new file mode 100644 index 0000000..6979e51 --- /dev/null +++ b/app/src/main/java/com/example/saipp/data/AssignmentRepository.kt @@ -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> = 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() + 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 = 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 = 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 = 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 = 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() + } + } +} diff --git a/app/src/main/java/com/example/saipp/data/BatteryRepository.kt b/app/src/main/java/com/example/saipp/data/BatteryRepository.kt index 8797b67..3437186 100644 --- a/app/src/main/java/com/example/saipp/data/BatteryRepository.kt +++ b/app/src/main/java/com/example/saipp/data/BatteryRepository.kt @@ -4,20 +4,22 @@ import com.example.saipp.data.model.Battery import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.sql.SQLException +import java.sql.Types class BatteryRepository { suspend fun getAll(): Result> = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) 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 resultSet = statement.executeQuery() val items = mutableListOf() while (resultSet.next()) { items.add(Battery( id = resultSet.getInt("id_bateria"), - model = resultSet.getString("modelo"), - status = resultSet.getString("estado") + idSai = resultSet.getInt("id_sai").takeIf { !resultSet.wasNull() }, + brand = resultSet.getString("marca"), + installDate = resultSet.getString("fecha_instalacion") )) } Result.success(items) @@ -31,10 +33,12 @@ class BatteryRepository { suspend fun insert(item: Battery): Result = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) 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) - statement.setString(1, item.model) - statement.setString(2, item.status) + if (item.idSai != null) statement.setInt(1, item.idSai) else statement.setNull(1, Types.INTEGER) + statement.setString(2, item.brand) + statement.setString(3, item.installDate) + if (statement.executeUpdate() > 0) Result.success(Unit) else Result.failure(Exception("no_rows_inserted")) } catch (e: SQLException) { @@ -47,11 +51,13 @@ class BatteryRepository { suspend fun update(item: Battery): Result = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) 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) - statement.setString(1, item.model) - statement.setString(2, item.status) - statement.setInt(3, item.id) + if (item.idSai != null) statement.setInt(1, item.idSai) else statement.setNull(1, Types.INTEGER) + statement.setString(2, item.brand) + statement.setString(3, item.installDate) + statement.setInt(4, item.id) + if (statement.executeUpdate() > 0) Result.success(Unit) else Result.failure(Exception("update_failed")) } catch (e: SQLException) { @@ -64,7 +70,7 @@ class BatteryRepository { suspend fun delete(id: Int): Result = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) try { - val sql = "DELETE FROM baterias WHERE id_bateria = ?" + val sql = "DELETE FROM Bateria WHERE id_bateria = ?" val statement = connection.prepareStatement(sql) statement.setInt(1, id) if (statement.executeUpdate() > 0) Result.success(Unit) @@ -75,4 +81,28 @@ class BatteryRepository { connection.close() } } + + suspend fun getById(id: Int): Result = withContext(Dispatchers.IO) { + val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) + try { + val sql = "SELECT id_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() + } + } } diff --git a/app/src/main/java/com/example/saipp/data/PersonRepository.kt b/app/src/main/java/com/example/saipp/data/PersonRepository.kt index 3f704ad..ee0f157 100644 --- a/app/src/main/java/com/example/saipp/data/PersonRepository.kt +++ b/app/src/main/java/com/example/saipp/data/PersonRepository.kt @@ -9,15 +9,14 @@ class PersonRepository { suspend fun getAll(): Result> = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) try { - val sql = "SELECT id_persona, nombre, puesto FROM personas" + val sql = "SELECT id_persona, nombre FROM Persona" val statement = connection.prepareStatement(sql) val resultSet = statement.executeQuery() val items = mutableListOf() while (resultSet.next()) { items.add(Person( id = resultSet.getInt("id_persona"), - name = resultSet.getString("nombre"), - position = resultSet.getString("puesto") + name = resultSet.getString("nombre") )) } Result.success(items) @@ -31,10 +30,9 @@ class PersonRepository { suspend fun insert(item: Person): Result = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) try { - val sql = "INSERT INTO personas (nombre, puesto) VALUES (?, ?)" + val sql = "INSERT INTO Persona (nombre) VALUES (?)" val statement = connection.prepareStatement(sql) statement.setString(1, item.name) - statement.setString(2, item.position) if (statement.executeUpdate() > 0) Result.success(Unit) else Result.failure(Exception("no_rows_inserted")) } catch (e: SQLException) { @@ -47,11 +45,10 @@ class PersonRepository { suspend fun update(item: Person): Result = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) 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) statement.setString(1, item.name) - statement.setString(2, item.position) - statement.setInt(3, item.id) + statement.setInt(2, item.id) if (statement.executeUpdate() > 0) Result.success(Unit) else Result.failure(Exception("update_failed")) } catch (e: SQLException) { @@ -64,7 +61,7 @@ class PersonRepository { suspend fun delete(id: Int): Result = withContext(Dispatchers.IO) { val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) try { - val sql = "DELETE FROM personas WHERE id_persona = ?" + val sql = "DELETE FROM Persona WHERE id_persona = ?" val statement = connection.prepareStatement(sql) statement.setInt(1, id) if (statement.executeUpdate() > 0) Result.success(Unit) @@ -75,4 +72,26 @@ class PersonRepository { connection.close() } } + + suspend fun getById(id: Int): Result = withContext(Dispatchers.IO) { + val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) + try { + val sql = "SELECT id_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() + } + } } diff --git a/app/src/main/java/com/example/saipp/data/SaiRepository.kt b/app/src/main/java/com/example/saipp/data/SaiRepository.kt index e684cda..0533e5f 100644 --- a/app/src/main/java/com/example/saipp/data/SaiRepository.kt +++ b/app/src/main/java/com/example/saipp/data/SaiRepository.kt @@ -74,4 +74,23 @@ class SaiRepository { connection.close() } } + + suspend fun getById(id: Int): Result = withContext(Dispatchers.IO) { + val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error")) + try { + val sql = "SELECT id_sai, ns FROM SAIs WHERE id_sai = ?" + val 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() + } + } } diff --git a/app/src/main/java/com/example/saipp/data/model/Assignment.kt b/app/src/main/java/com/example/saipp/data/model/Assignment.kt new file mode 100644 index 0000000..53955a4 --- /dev/null +++ b/app/src/main/java/com/example/saipp/data/model/Assignment.kt @@ -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 +) diff --git a/app/src/main/java/com/example/saipp/data/model/Battery.kt b/app/src/main/java/com/example/saipp/data/model/Battery.kt index 9a0bc3c..f9f018b 100644 --- a/app/src/main/java/com/example/saipp/data/model/Battery.kt +++ b/app/src/main/java/com/example/saipp/data/model/Battery.kt @@ -2,6 +2,7 @@ package com.example.saipp.data.model data class Battery( val id: Int = 0, - val model: String, - val status: String + val idSai: Int? = null, + val brand: String? = null, + val installDate: String? = null ) diff --git a/app/src/main/java/com/example/saipp/data/model/Person.kt b/app/src/main/java/com/example/saipp/data/model/Person.kt index 536a9a3..3ff1cca 100644 --- a/app/src/main/java/com/example/saipp/data/model/Person.kt +++ b/app/src/main/java/com/example/saipp/data/model/Person.kt @@ -2,6 +2,5 @@ package com.example.saipp.data.model data class Person( val id: Int = 0, - val name: String, - val position: String + val name: String ) diff --git a/app/src/main/java/com/example/saipp/ui/AppNavigation.kt b/app/src/main/java/com/example/saipp/ui/AppNavigation.kt index 38b8011..8ab7697 100644 --- a/app/src/main/java/com/example/saipp/ui/AppNavigation.kt +++ b/app/src/main/java/com/example/saipp/ui/AppNavigation.kt @@ -1,19 +1,35 @@ package com.example.saipp.ui import androidx.compose.runtime.Composable +import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument import com.example.saipp.ui.management.* sealed class Screen(val route: String) { object Home : Screen("home") object SaiList : Screen("sai_list") object SaiScanner : Screen("sai_scanner") + object EditSai : Screen("edit_sai/{saiId}") { + fun createRoute(id: Int) = "edit_sai/$id" + } object BatteryList : Screen("battery_list") object AddBattery : Screen("add_battery") + object EditBattery : Screen("edit_battery/{batteryId}") { + fun createRoute(id: Int) = "edit_battery/$id" + } object PersonList : Screen("person_list") 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 @@ -25,13 +41,17 @@ fun AppNavigation() { HomeScreen( onNavigateToSais = { navController.navigate(Screen.SaiList.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) { SaiListScreen( 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) { @@ -39,27 +59,69 @@ fun AppNavigation() { 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) { BatteryListScreen( 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) { - AddBatteryScreen( - onNavigateBack = { navController.popBackStack() } - ) + AddBatteryScreen(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) { PersonListScreen( 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) { - AddPersonScreen( - onNavigateBack = { navController.popBackStack() } + AddPersonScreen(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() }) + } } } diff --git a/app/src/main/java/com/example/saipp/ui/management/AddAssignmentScreen.kt b/app/src/main/java/com/example/saipp/ui/management/AddAssignmentScreen.kt new file mode 100644 index 0000000..aa18886 --- /dev/null +++ b/app/src/main/java/com/example/saipp/ui/management/AddAssignmentScreen.kt @@ -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(null) } + var selectedPersonId by remember { mutableStateOf(null) } + var assignedDate by remember { mutableStateOf("") } + var returnDate by remember { mutableStateOf("") } + + var sais by remember { mutableStateOf>(emptyList()) } + var people by remember { mutableStateOf>(emptyList()) } + var isSaving by remember { mutableStateOf(false) } + var saiExpanded by remember { mutableStateOf(false) } + var personExpanded by remember { mutableStateOf(false) } + + 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") + } + } + } +} diff --git a/app/src/main/java/com/example/saipp/ui/management/AddBatteryScreen.kt b/app/src/main/java/com/example/saipp/ui/management/AddBatteryScreen.kt index ad8ec53..59fd26e 100644 --- a/app/src/main/java/com/example/saipp/ui/management/AddBatteryScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/AddBatteryScreen.kt @@ -8,9 +8,13 @@ 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) @@ -20,11 +24,20 @@ fun AddBatteryScreen( ) { val context = LocalContext.current val scope = rememberCoroutineScope() - val repository = remember { BatteryRepository() } + val batteryRepository = remember { BatteryRepository() } + val saiRepository = remember { SaiRepository() } - var model by remember { mutableStateOf("") } - var status by remember { mutableStateOf("") } + var brand by remember { mutableStateOf("") } + var installDate by remember { mutableStateOf("") } + var selectedSaiId by remember { mutableStateOf(null) } + + var sais by remember { mutableStateOf>(emptyList()) } var isSaving by remember { mutableStateOf(false) } + var expanded by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + saiRepository.getAll().onSuccess { sais = it } + } Scaffold( topBar = { @@ -44,33 +57,80 @@ fun AddBatteryScreen( .padding(padding) .padding(16.dp) ) { - OutlinedTextField( - value = model, - onValueChange = { model = it }, - label = { Text("Modelo") }, - modifier = Modifier.fillMaxWidth() - ) + // 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 = status, - onValueChange = { status = it }, - label = { Text("Estado") }, + 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 = { - if (model.isNotBlank() && status.isNotBlank()) { - isSaving = true - scope.launch { - repository.insert(Battery(model = model, status = status)) - .onSuccess { - 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 + isSaving = true + scope.launch { + batteryRepository.insert( + Battery( + idSai = selectedSaiId, + brand = brand, + installDate = installDate.takeIf { it.isNotBlank() } + ) + ).onSuccess { + 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(), diff --git a/app/src/main/java/com/example/saipp/ui/management/AddPersonScreen.kt b/app/src/main/java/com/example/saipp/ui/management/AddPersonScreen.kt index 905223a..9be9c9d 100644 --- a/app/src/main/java/com/example/saipp/ui/management/AddPersonScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/AddPersonScreen.kt @@ -23,7 +23,6 @@ fun AddPersonScreen( val repository = remember { PersonRepository() } var name by remember { mutableStateOf("") } - var position by remember { mutableStateOf("") } var isSaving by remember { mutableStateOf(false) } Scaffold( @@ -50,20 +49,13 @@ fun AddPersonScreen( label = { Text("Nombre") }, 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)) Button( onClick = { - if (name.isNotBlank() && position.isNotBlank()) { + if (name.isNotBlank()) { isSaving = true scope.launch { - repository.insert(Person(name = name, position = position)) + repository.insert(Person(name = name)) .onSuccess { Toast.makeText(context, "Persona añadida", Toast.LENGTH_SHORT).show() onNavigateBack() diff --git a/app/src/main/java/com/example/saipp/ui/management/AssignmentListScreen.kt b/app/src/main/java/com/example/saipp/ui/management/AssignmentListScreen.kt new file mode 100644 index 0000000..d6b7f3a --- /dev/null +++ b/app/src/main/java/com/example/saipp/ui/management/AssignmentListScreen.kt @@ -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>(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() + } + } + } + } +} diff --git a/app/src/main/java/com/example/saipp/ui/management/BatteryListScreen.kt b/app/src/main/java/com/example/saipp/ui/management/BatteryListScreen.kt index f962386..63843ae 100644 --- a/app/src/main/java/com/example/saipp/ui/management/BatteryListScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/BatteryListScreen.kt @@ -8,6 +8,7 @@ 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 @@ -22,7 +23,8 @@ import kotlinx.coroutines.launch @Composable fun BatteryListScreen( onNavigateBack: () -> Unit, - onNavigateToAdd: () -> Unit + onNavigateToAdd: () -> Unit, + onNavigateToEdit: (Int) -> Unit ) { val context = LocalContext.current val scope = rememberCoroutineScope() @@ -73,15 +75,25 @@ fun BatteryListScreen( ) { items(items) { item -> ListItem( - headlineContent = { Text("Modelo: ${item.model}") }, - supportingContent = { Text("Estado: ${item.status}") }, + headlineContent = { Text("Marca: ${item.brand ?: "Desconocida"}") }, + supportingContent = { + Column { + Text("Instalación: ${item.installDate ?: "N/A"}") + item.idSai?.let { Text("SAI ID: $it") } + } + }, trailingContent = { - IconButton(onClick = { - scope.launch { - repository.delete(item.id).onSuccess { loadItems() } + 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") } } ) diff --git a/app/src/main/java/com/example/saipp/ui/management/EditAssignmentScreen.kt b/app/src/main/java/com/example/saipp/ui/management/EditAssignmentScreen.kt new file mode 100644 index 0000000..e4594a3 --- /dev/null +++ b/app/src/main/java/com/example/saipp/ui/management/EditAssignmentScreen.kt @@ -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(null) } + var selectedPersonId by remember { mutableStateOf(null) } + var assignedDate by remember { mutableStateOf("") } + var returnDate by remember { mutableStateOf("") } + + var sais by remember { mutableStateOf>(emptyList()) } + var people by remember { mutableStateOf>(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") + } + } + } + } +} diff --git a/app/src/main/java/com/example/saipp/ui/management/EditBatteryScreen.kt b/app/src/main/java/com/example/saipp/ui/management/EditBatteryScreen.kt new file mode 100644 index 0000000..677215c --- /dev/null +++ b/app/src/main/java/com/example/saipp/ui/management/EditBatteryScreen.kt @@ -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(null) } + + var sais by remember { mutableStateOf>(emptyList()) } + var isLoading by remember { mutableStateOf(true) } + var isSaving by remember { mutableStateOf(false) } + var expanded by remember { mutableStateOf(false) } + + 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") + } + } + } + } +} diff --git a/app/src/main/java/com/example/saipp/ui/management/EditPersonScreen.kt b/app/src/main/java/com/example/saipp/ui/management/EditPersonScreen.kt new file mode 100644 index 0000000..6c7f2a5 --- /dev/null +++ b/app/src/main/java/com/example/saipp/ui/management/EditPersonScreen.kt @@ -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") + } + } + } + } +} diff --git a/app/src/main/java/com/example/saipp/ui/management/EditSaiScreen.kt b/app/src/main/java/com/example/saipp/ui/management/EditSaiScreen.kt new file mode 100644 index 0000000..f4747e3 --- /dev/null +++ b/app/src/main/java/com/example/saipp/ui/management/EditSaiScreen.kt @@ -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") + } + } + } + } +} diff --git a/app/src/main/java/com/example/saipp/ui/management/HomeScreen.kt b/app/src/main/java/com/example/saipp/ui/management/HomeScreen.kt index dd1d008..9aaef0e 100644 --- a/app/src/main/java/com/example/saipp/ui/management/HomeScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/HomeScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.example.saipp.ui.ConnectionIndicator @@ -17,7 +16,8 @@ import com.example.saipp.data.MariaDbConnection fun HomeScreen( onNavigateToSais: () -> Unit, onNavigateToBatteries: () -> Unit, - onNavigateToPeople: () -> Unit + onNavigateToPeople: () -> Unit, + onNavigateToAssignments: () -> Unit ) { var connectionStatus by remember { mutableStateOf?>(null) } var isCheckingConnection by remember { mutableStateOf(true) } @@ -49,7 +49,7 @@ fun HomeScreen( Spacer(modifier = Modifier.height(32.dp)) MenuButton( - text = "Gestión de SAIs", + text = stringResource(R.string.menu_sais), onClick = onNavigateToSais, modifier = Modifier.fillMaxWidth() ) @@ -57,7 +57,7 @@ fun HomeScreen( Spacer(modifier = Modifier.height(16.dp)) MenuButton( - text = "Gestión de Baterías", + text = stringResource(R.string.menu_batteries), onClick = onNavigateToBatteries, modifier = Modifier.fillMaxWidth() ) @@ -65,10 +65,18 @@ fun HomeScreen( Spacer(modifier = Modifier.height(16.dp)) MenuButton( - text = "Gestión de Personas", + text = stringResource(R.string.menu_people), onClick = onNavigateToPeople, modifier = Modifier.fillMaxWidth() ) + + Spacer(modifier = Modifier.height(16.dp)) + + MenuButton( + text = stringResource(R.string.menu_assignments), + onClick = onNavigateToAssignments, + modifier = Modifier.fillMaxWidth() + ) } } } diff --git a/app/src/main/java/com/example/saipp/ui/management/PersonListScreen.kt b/app/src/main/java/com/example/saipp/ui/management/PersonListScreen.kt index 7b88372..29d8025 100644 --- a/app/src/main/java/com/example/saipp/ui/management/PersonListScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/PersonListScreen.kt @@ -8,6 +8,7 @@ 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 @@ -22,7 +23,8 @@ import kotlinx.coroutines.launch @Composable fun PersonListScreen( onNavigateBack: () -> Unit, - onNavigateToAdd: () -> Unit + onNavigateToAdd: () -> Unit, + onNavigateToEdit: (Int) -> Unit ) { val context = LocalContext.current val scope = rememberCoroutineScope() @@ -74,14 +76,18 @@ fun PersonListScreen( items(items) { item -> ListItem( headlineContent = { Text(item.name) }, - supportingContent = { Text(item.position) }, trailingContent = { - IconButton(onClick = { - scope.launch { - repository.delete(item.id).onSuccess { loadItems() } + 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") } } ) diff --git a/app/src/main/java/com/example/saipp/ui/management/SaiListScreen.kt b/app/src/main/java/com/example/saipp/ui/management/SaiListScreen.kt index 1aef99b..2964a65 100644 --- a/app/src/main/java/com/example/saipp/ui/management/SaiListScreen.kt +++ b/app/src/main/java/com/example/saipp/ui/management/SaiListScreen.kt @@ -8,6 +8,7 @@ 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 @@ -22,7 +23,8 @@ import kotlinx.coroutines.launch @Composable fun SaiListScreen( onNavigateBack: () -> Unit, - onNavigateToScan: () -> Unit + onNavigateToScan: () -> Unit, + onNavigateToEdit: (Int) -> Unit ) { val context = LocalContext.current val scope = rememberCoroutineScope() @@ -75,12 +77,17 @@ fun SaiListScreen( ListItem( headlineContent = { Text("NS: ${sai.ns}") }, trailingContent = { - IconButton(onClick = { - scope.launch { - repository.delete(sai.id).onSuccess { loadSais() } + 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") } } ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 87d6a6b..8447b73 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,4 +13,19 @@ No se pudo establecer conexión con el servidor Error de conexión a la base de datos No se insertaron filas en la base de datos + SAI + Marca + Fecha Instalación (AAAA-MM-DD HH:MM:SS) + Seleccionar SAI + Ninguno + + Gestión de SAIs + Gestión de Baterías + Gestión de Personas + Gestión de Asignaciones + + Persona + Fecha Asignación (AAAA-MM-DD) + Fecha Devolución (AAAA-MM-DD) + Seleccionar Persona \ No newline at end of file