diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileHomeScreen.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileHomeScreen.kt new file mode 100644 index 0000000..3caec53 --- /dev/null +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileHomeScreen.kt @@ -0,0 +1,231 @@ +package com.korexlabs.dhinspeccion.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.AssistChip +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.korexlabs.dhinspeccion.MainViewModel +import com.korexlabs.dhinspeccion.data.DhRepository +import com.korexlabs.dhinspeccion.data.MobileInspectionOpenRepository +import com.korexlabs.dhinspeccion.data.MobilePlanningAsset +import com.korexlabs.dhinspeccion.data.VisitSummary +import kotlinx.coroutines.launch + +@Composable +fun MobileHomeScreen(model: MainViewModel) { + val session = model.session ?: return + val context = LocalContext.current + val scope = rememberCoroutineScope() + val openRepository = remember(context) { MobileInspectionOpenRepository(context) } + + var showOpen by rememberSaveable { mutableStateOf(false) } + var areas by remember { mutableStateOf>(emptyList()) } + var operators by remember { mutableStateOf>(emptyList()) } + var selectedAreaId by rememberSaveable { mutableStateOf(null) } + var selectedCompanyId by rememberSaveable { mutableStateOf(null) } + var loadingContext by remember { mutableStateOf(false) } + var opening by remember { mutableStateOf(false) } + var localError by remember { mutableStateOf(null) } + + fun loadAreas() { + scope.launch { + loadingContext = true + localError = null + runCatching { openRepository.areas().data } + .onSuccess { areas = it } + .onFailure { localError = DhRepository.humanError(it) } + loadingContext = false + } + } + + fun chooseArea(areaId: String) { + selectedAreaId = areaId + selectedCompanyId = null + operators = emptyList() + scope.launch { + loadingContext = true + localError = null + runCatching { openRepository.operators(areaId).data } + .onSuccess { operators = it } + .onFailure { localError = DhRepository.humanError(it) } + loadingContext = false + } + } + + LaunchedEffect(showOpen) { + if (showOpen && areas.isEmpty()) loadAreas() + } + + Column(Modifier.fillMaxSize().padding(top = 28.dp)) { + Row( + Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Column { + Text("Mis inspecciones", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) + Text(session.displayName, style = MaterialTheme.typography.bodySmall) + } + Row { + OutlinedButton(onClick = { model.loadVisits() }, enabled = !model.busy && !opening) { + Text("Actualizar") + } + Spacer(Modifier.width(8.dp)) + OutlinedButton(onClick = { model.logout() }, enabled = !opening) { Text("Salir") } + } + } + + Column( + Modifier.fillMaxWidth().padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + model.error?.let { Text(it, color = MaterialTheme.colorScheme.error) } + model.notice?.let { Text(it, color = MaterialTheme.colorScheme.primary) } + localError?.let { Text(it, color = MaterialTheme.colorScheme.error) } + + Button( + onClick = { + showOpen = !showOpen + localError = null + }, + modifier = Modifier.fillMaxWidth(), + enabled = !opening, + ) { + Text(if (showOpen) "Cancelar nueva inspección" else "Abrir inspección") + } + + if (showOpen) { + Card(Modifier.fillMaxWidth()) { + Column( + Modifier.fillMaxWidth().padding(14.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Text("Abrir inspección en campo", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + Text( + "Elegí el Área y la Operadora vigente. La inspección se crea autoasignada a vos y queda iniciada con la fecha y hora del servidor.", + style = MaterialTheme.typography.bodySmall, + ) + + Text("1. Área", fontWeight = FontWeight.SemiBold) + if (loadingContext && areas.isEmpty()) CircularProgressIndicator() + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + items(areas, key = { it.id }) { area -> + AssistChip( + onClick = { chooseArea(area.id) }, + label = { Text(if (area.id == selectedAreaId) "✓ ${area.name}" else area.name) }, + ) + } + } + + if (selectedAreaId != null) { + Text("2. Operadora", fontWeight = FontWeight.SemiBold) + if (loadingContext && operators.isEmpty()) { + CircularProgressIndicator() + } else if (operators.isEmpty()) { + Text("No hay una Operadora vigente para el Área seleccionada.", color = MaterialTheme.colorScheme.error) + } + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + items(operators, key = { it.id }) { company -> + AssistChip( + onClick = { selectedCompanyId = company.id }, + label = { Text(if (company.id == selectedCompanyId) "✓ ${company.name}" else company.name) }, + ) + } + } + } + + Button( + onClick = { + val areaId = selectedAreaId ?: return@Button + val companyId = selectedCompanyId ?: return@Button + scope.launch { + opening = true + localError = null + runCatching { openRepository.open(areaId, companyId) } + .onSuccess { opened -> + showOpen = false + model.openVisit(opened.id) + } + .onFailure { localError = DhRepository.humanError(it) } + opening = false + } + }, + modifier = Modifier.fillMaxWidth(), + enabled = selectedAreaId != null && selectedCompanyId != null && !opening && !loadingContext, + ) { + Text(if (opening) "Abriendo…" else "Abrir inspección ahora") + } + } + } + } + } + + if (model.busy && model.visits.isEmpty()) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() } + } else if (model.visits.isEmpty()) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("No tenés inspecciones asignadas. Podés abrir una nueva desde esta tablet.") + } + } else { + LazyColumn( + Modifier.fillMaxSize().padding(horizontal = 16.dp, vertical = 10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + items(model.visits, key = { it.id }) { visit -> + MobileVisitCard(visit) { model.openVisit(visit.id) } + } + item { Spacer(Modifier.height(24.dp)) } + } + } + } +} + +@Composable +private fun MobileVisitCard(visit: VisitSummary, onOpen: () -> Unit) { + Card(onClick = onOpen, modifier = Modifier.fillMaxWidth()) { + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(5.dp)) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text(visit.code, fontWeight = FontWeight.Bold) + Text(visit.status) + } + Text(visit.operatorCompany?.name ?: "Operadora sin definir") + Text(visit.operationalArea?.name ?: "Área sin definir", style = MaterialTheme.typography.bodySmall) + visit.plannedStartAt?.let { + Text("Prevista: ${it.replace('T', ' ').take(16)}", style = MaterialTheme.typography.bodySmall) + } + Text( + "Inventario: ${visit.assetCount} · Equipo inspector: ${visit.memberCount}", + style = MaterialTheme.typography.bodySmall, + ) + } + } +}