|
|
|
@@ -16,6 +16,7 @@ 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.heightIn
|
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
|
import androidx.compose.foundation.layout.width
|
|
|
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
|
|
@@ -36,6 +37,11 @@ import androidx.compose.material.icons.filled.LocationOn
|
|
|
|
|
import androidx.compose.material.icons.filled.Search
|
|
|
|
|
import androidx.compose.material.icons.filled.WarningAmber
|
|
|
|
|
import androidx.compose.material3.Button
|
|
|
|
|
import androidx.compose.material3.DropdownMenuItem
|
|
|
|
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
|
|
|
import androidx.compose.material3.ExposedDropdownMenuBox
|
|
|
|
|
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
|
|
|
|
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
|
|
|
|
import androidx.compose.material3.ElevatedCard
|
|
|
|
|
import androidx.compose.material3.FilledTonalButton
|
|
|
|
|
import androidx.compose.material3.HorizontalDivider
|
|
|
|
@@ -259,6 +265,7 @@ private fun ModernChecklistCard(visit: VisitDetail) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
|
|
|
@Composable
|
|
|
|
|
private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit) {
|
|
|
|
|
val context = LocalContext.current
|
|
|
|
@@ -279,6 +286,7 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
|
|
|
|
|
var selectedTypeId by rememberSaveable(visit.id) { mutableStateOf<String?>(null) }
|
|
|
|
|
var selectedFamilyId by rememberSaveable(visit.id) { mutableStateOf<String?>(null) }
|
|
|
|
|
var familySearch by rememberSaveable(visit.id) { mutableStateOf("") }
|
|
|
|
|
var familyExpanded by remember { mutableStateOf(false) }
|
|
|
|
|
val attributeValues = remember { mutableStateMapOf<String, String>() }
|
|
|
|
|
var pendingAutoPhoto by rememberSaveable(visit.id) { mutableStateOf(false) }
|
|
|
|
|
var localError by rememberSaveable(visit.id) { mutableStateOf<String?>(null) }
|
|
|
|
@@ -295,6 +303,7 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
|
|
|
|
|
selectedTypeId = null
|
|
|
|
|
selectedFamilyId = null
|
|
|
|
|
familySearch = ""
|
|
|
|
|
familyExpanded = false
|
|
|
|
|
attributeValues.clear()
|
|
|
|
|
localError = null
|
|
|
|
|
}
|
|
|
|
@@ -379,6 +388,9 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val selectedType = model.fieldTypes.firstOrNull { it.id == selectedTypeId }
|
|
|
|
|
val editableAttributes = selectedType?.attributes.orEmpty().filterNot {
|
|
|
|
|
selectedType?.familyRequired == true && it.code == "tipo_instalacion"
|
|
|
|
|
}
|
|
|
|
|
val selectedFamily = selectedType?.families?.firstOrNull { it.id == selectedFamilyId }
|
|
|
|
|
val filteredFamilies = remember(selectedType, familySearch) {
|
|
|
|
|
val needle = modernNormalize(familySearch)
|
|
|
|
@@ -746,37 +758,51 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selectedType?.familyRequired == true) {
|
|
|
|
|
Text("Clasificación técnica", fontWeight = FontWeight.Bold)
|
|
|
|
|
ExposedDropdownMenuBox(
|
|
|
|
|
expanded = familyExpanded && !model.busy,
|
|
|
|
|
onExpandedChange = {
|
|
|
|
|
if (!model.busy) {
|
|
|
|
|
familyExpanded = it
|
|
|
|
|
familySearch = ""
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
) {
|
|
|
|
|
OutlinedTextField(
|
|
|
|
|
value = familySearch,
|
|
|
|
|
onValueChange = { familySearch = it },
|
|
|
|
|
label = { Text("Buscar clasificación") },
|
|
|
|
|
value = if (familyExpanded) familySearch else selectedFamily?.name.orEmpty(),
|
|
|
|
|
onValueChange = { familySearch = it; familyExpanded = true },
|
|
|
|
|
label = { Text(if (isSubinstallation) "Tipo de subinstalación" else "Tipo de instalación") },
|
|
|
|
|
placeholder = { Text("Buscar por nombre o código") },
|
|
|
|
|
leadingIcon = { Icon(Icons.Filled.Search, null) },
|
|
|
|
|
supportingText = { Text("${filteredFamilies.size} opciones compatibles") },
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = familyExpanded) },
|
|
|
|
|
enabled = !model.busy,
|
|
|
|
|
modifier = Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable),
|
|
|
|
|
singleLine = true,
|
|
|
|
|
)
|
|
|
|
|
filteredFamilies.take(12).forEach { family ->
|
|
|
|
|
Surface(
|
|
|
|
|
onClick = { selectedFamilyId = family.id },
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
shape = MaterialTheme.shapes.medium,
|
|
|
|
|
color = if (selectedFamilyId == family.id) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface,
|
|
|
|
|
border = BorderStroke(1.dp, if (selectedFamilyId == family.id) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant),
|
|
|
|
|
ExposedDropdownMenu(
|
|
|
|
|
expanded = familyExpanded && !model.busy,
|
|
|
|
|
onDismissRequest = { familyExpanded = false; familySearch = "" },
|
|
|
|
|
modifier = Modifier.heightIn(max = 280.dp),
|
|
|
|
|
) {
|
|
|
|
|
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
|
Text(
|
|
|
|
|
(if (family.isOther) "Otro · " else "") + family.name,
|
|
|
|
|
Modifier.weight(1f),
|
|
|
|
|
fontWeight = if (selectedFamilyId == family.id) FontWeight.Bold else FontWeight.Normal,
|
|
|
|
|
if (filteredFamilies.isEmpty()) {
|
|
|
|
|
DropdownMenuItem(text = { Text("Sin coincidencias") }, onClick = {}, enabled = false)
|
|
|
|
|
}
|
|
|
|
|
filteredFamilies.forEach { family ->
|
|
|
|
|
DropdownMenuItem(
|
|
|
|
|
text = { Text((if (family.isOther) "Otro · " else "") + family.name) },
|
|
|
|
|
trailingIcon = {
|
|
|
|
|
if (selectedFamilyId == family.id) Icon(Icons.Filled.CheckCircle, "Seleccionado")
|
|
|
|
|
},
|
|
|
|
|
onClick = {
|
|
|
|
|
selectedFamilyId = family.id
|
|
|
|
|
familyExpanded = false
|
|
|
|
|
familySearch = ""
|
|
|
|
|
keyboard?.hide()
|
|
|
|
|
focusManager.clearFocus()
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
if (selectedFamilyId == family.id) Icon(Icons.Filled.CheckCircle, null, tint = MaterialTheme.colorScheme.primary)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (filteredFamilies.size > 12) {
|
|
|
|
|
Text("Seguí escribiendo para reducir la lista.", style = MaterialTheme.typography.bodySmall)
|
|
|
|
|
}
|
|
|
|
|
selectedFamily?.let { family ->
|
|
|
|
|
if (family.isOther) {
|
|
|
|
|
Text("Quedará marcado para revisión en oficina.", color = MaterialTheme.colorScheme.secondary)
|
|
|
|
@@ -811,7 +837,7 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
|
|
|
|
|
minLines = 2,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
selectedType?.attributes?.forEach { definition ->
|
|
|
|
|
editableAttributes.forEach { definition ->
|
|
|
|
|
OutlinedTextField(
|
|
|
|
|
value = attributeValues[definition.code].orEmpty(),
|
|
|
|
|
onValueChange = { attributeValues[definition.code] = it },
|
|
|
|
@@ -840,10 +866,9 @@ private fun ModernFieldInventoryScreen(model: MainViewModel, onBack: () -> Unit)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val attributesReady = selectedType?.attributes
|
|
|
|
|
?.filter { it.isRequired }
|
|
|
|
|
?.all { attributeValues[it.code].orEmpty().isNotBlank() }
|
|
|
|
|
?: false
|
|
|
|
|
val attributesReady = editableAttributes
|
|
|
|
|
.filter { it.isRequired }
|
|
|
|
|
.all { attributeValues[it.code].orEmpty().isNotBlank() }
|
|
|
|
|
val familyReady = selectedType?.familyRequired != true || selectedFamilyId != null
|
|
|
|
|
Button(
|
|
|
|
|
onClick = { requestCreate() },
|
|
|
|
|