feat: filter out deleted fields in ListDetailScreen and webRoutes for improved data integrity

This commit is contained in:
2025-10-31 22:12:21 +01:00
parent 1e628a3a16
commit 62077840d1
4 changed files with 98 additions and 36 deletions
@@ -69,11 +69,12 @@ data class Field(
val isDeleted: Boolean = false,
) {
fun getType(): FieldType {
val raw = fieldType.ifBlank { "TEXT" }.uppercase()
return try {
FieldType.valueOf(fieldType)
FieldType.valueOf(raw)
} catch (e: Exception) {
// Handle legacy field types
when (fieldType) {
// Handle legacy/synonym field types
when (raw) {
"STRING" -> FieldType.TEXT
"PRICE" -> FieldType.CURRENCY
"AMOUNT" -> FieldType.NUMBER
@@ -84,7 +85,7 @@ data class Field(
}
fun getDropdownOptions(): List<String> {
return if (fieldType == "DROPDOWN" && fieldOptions.isNotBlank()) {
return if (getType() == FieldType.DROPDOWN && fieldOptions.isNotBlank()) {
fieldOptions.split("|")
} else {
emptyList()
@@ -92,7 +93,7 @@ data class Field(
}
fun getCurrency(): String {
return if ((fieldType == "CURRENCY" || fieldType == "PRICE") && fieldOptions.isNotBlank()) {
return if ((getType() == FieldType.CURRENCY) && fieldOptions.isNotBlank()) {
fieldOptions
} else {
"CHF"
@@ -100,7 +101,7 @@ data class Field(
}
fun getMaxRating(): Int {
return if (fieldType == "RATING" && fieldOptions.isNotBlank()) {
return if (getType() == FieldType.RATING && fieldOptions.isNotBlank()) {
fieldOptions.toIntOrNull() ?: 5
} else {
5
@@ -108,7 +109,7 @@ data class Field(
}
fun getAutocompleteOptions(): List<String> {
return if (fieldType == "AUTOCOMPLETE" && fieldOptions.isNotBlank()) {
return if (getType() == FieldType.AUTOCOMPLETE && fieldOptions.isNotBlank()) {
fieldOptions.split("|")
} else {
emptyList()
@@ -1345,13 +1345,23 @@ fun AddFieldDialog(
"CURRENCY" -> "Currency"
"PERCENTAGE" -> "Percentage"
"DROPDOWN" -> "Dropdown"
"AUTOCOMPLETE" -> "Autocomplete"
"CHECKBOX" -> "Checkbox"
"SWITCH" -> "Switch"
"URL" -> "URL"
"EMAIL" -> "Email"
"PHONE" -> "Phone"
"DATE" -> "Date"
"TIME" -> "Time"
"DATETIME" -> "Date & Time"
"DURATION" -> "Duration"
"IMAGE" -> "Image"
"FILE" -> "File"
"BARCODE" -> "Barcode"
"SIGNATURE" -> "Signature"
"RATING" -> "Rating"
"COLOR" -> "Color"
"LOCATION" -> "Location"
else -> "Text"
},
onValueChange = {},
@@ -1599,8 +1609,9 @@ fun AddFieldDialog(
TextButton(
onClick = {
if (fieldName.isNotBlank()) {
val normalizedType = selectedFieldType.uppercase()
val options =
when (selectedFieldType) {
when (normalizedType) {
"CURRENCY" -> currency.trim()
"DROPDOWN" ->
dropdownOptions.split(",")
@@ -1615,12 +1626,12 @@ fun AddFieldDialog(
"RATING" -> currency.trim().ifBlank { "5" }
else -> ""
}
onAdd(fieldName.trim(), selectedFieldType, options)
onAdd(fieldName.trim(), normalizedType, options)
}
},
enabled =
fieldName.isNotBlank() &&
(selectedFieldType !in listOf("DROPDOWN", "AUTOCOMPLETE") || dropdownOptions.isNotBlank()),
(selectedFieldType.uppercase() !in listOf("DROPDOWN", "AUTOCOMPLETE") || dropdownOptions.isNotBlank()),
) {
Text(stringResource(R.string.add))
}
@@ -1641,7 +1652,7 @@ fun EditFieldDialog(
onUpdate: (String, String, String) -> Unit,
) {
var name by remember { mutableStateOf(field.name) }
var selectedFieldType by remember { mutableStateOf(field.fieldType ?: "TEXT") }
var selectedFieldType by remember { mutableStateOf(field.fieldType?.uppercase() ?: "TEXT") }
var dropdownOptions by remember { mutableStateOf(field.getDropdownOptions().joinToString(", ")) }
var currency by remember { mutableStateOf(field.getCurrency()) }
var expanded by remember { mutableStateOf(false) }
@@ -1676,13 +1687,23 @@ fun EditFieldDialog(
"CURRENCY", "PRICE" -> "Currency"
"PERCENTAGE" -> "Percentage"
"DROPDOWN" -> "Dropdown"
"AUTOCOMPLETE" -> "Autocomplete"
"CHECKBOX" -> "Checkbox"
"SWITCH" -> "Switch"
"URL" -> "URL"
"EMAIL" -> "Email"
"PHONE" -> "Phone"
"DATE" -> "Date"
"TIME" -> "Time"
"DATETIME" -> "Date & Time"
"DURATION" -> "Duration"
"IMAGE" -> "Image"
"FILE" -> "File"
"BARCODE" -> "Barcode"
"SIGNATURE" -> "Signature"
"RATING" -> "Rating"
"COLOR" -> "Color"
"LOCATION" -> "Location"
else -> "Text"
},
onValueChange = {},
@@ -1929,8 +1950,9 @@ fun EditFieldDialog(
confirmButton = {
TextButton(
onClick = {
val normalizedType = selectedFieldType.uppercase()
val options =
when (selectedFieldType) {
when (normalizedType) {
"CURRENCY", "PRICE" -> currency.trim()
"DROPDOWN" ->
dropdownOptions.split(",")
@@ -1945,7 +1967,7 @@ fun EditFieldDialog(
"RATING" -> currency.trim().ifBlank { "5" }
else -> ""
}
onUpdate(name.trim(), selectedFieldType, options)
onUpdate(name.trim(), normalizedType, options)
},
enabled = name.isNotBlank() && (selectedFieldType !in listOf("DROPDOWN", "AUTOCOMPLETE") || dropdownOptions.isNotBlank()),
) {
@@ -130,12 +130,15 @@ class ListDetailViewModel(
emptyList()
}
// Insert atomically to avoid intermediate inconsistent states
// Insert atomically to avoid intermediate inconsistent states and bump list.updatedAt
database.withTransaction {
database.fieldDao().insertField(newField)
if (newValues.isNotEmpty()) {
database.itemValueDao().insertValues(newValues)
}
database.listDao().getListById(listId)?.let { l ->
database.listDao().updateList(l.copy(updatedAt = timestamp))
}
}
performSync()
@@ -144,7 +147,13 @@ class ListDetailViewModel(
fun deleteField(fieldId: String) {
viewModelScope.launch {
database.fieldDao().softDeleteField(fieldId, System.currentTimeMillis())
val ts = System.currentTimeMillis()
database.withTransaction {
database.fieldDao().softDeleteField(fieldId, ts)
database.listDao().getListById(listId)?.let { l ->
database.listDao().updateList(l.copy(updatedAt = ts))
}
}
performSync()
}
}
@@ -158,14 +167,20 @@ class ListDetailViewModel(
viewModelScope.launch {
val field = database.fieldDao().getFieldById(fieldId)
if (field != null) {
database.fieldDao().updateField(
field.copy(
name = name,
fieldType = fieldType,
fieldOptions = fieldOptions,
updatedAt = System.currentTimeMillis(),
),
)
val ts = System.currentTimeMillis()
database.withTransaction {
database.fieldDao().updateField(
field.copy(
name = name,
fieldType = fieldType,
fieldOptions = fieldOptions,
updatedAt = ts,
),
)
database.listDao().getListById(listId)?.let { l ->
database.listDao().updateList(l.copy(updatedAt = ts))
}
}
performSync()
}
}
@@ -198,6 +213,9 @@ class ListDetailViewModel(
if (values.isNotEmpty()) {
database.itemValueDao().insertValues(values)
}
database.listDao().getListById(listId)?.let { l ->
database.listDao().updateList(l.copy(updatedAt = timestamp))
}
}
performSync()
}
@@ -230,6 +248,9 @@ class ListDetailViewModel(
if (values.isNotEmpty()) {
database.itemValueDao().insertValues(values)
}
database.listDao().getListById(listId)?.let { l ->
database.listDao().updateList(l.copy(updatedAt = timestamp))
}
}
performSync()
}
@@ -242,12 +263,18 @@ class ListDetailViewModel(
viewModelScope.launch {
val itemValue = database.itemValueDao().getValueById(itemValueId)
if (itemValue != null) {
database.itemValueDao().updateValue(
itemValue.copy(
value = newValue,
updatedAt = System.currentTimeMillis(),
),
)
val ts = System.currentTimeMillis()
database.withTransaction {
database.itemValueDao().updateValue(
itemValue.copy(
value = newValue,
updatedAt = ts,
),
)
database.listDao().getListById(listId)?.let { l ->
database.listDao().updateList(l.copy(updatedAt = ts))
}
}
performSync()
}
}
@@ -255,7 +282,13 @@ class ListDetailViewModel(
fun deleteItem(itemId: String) {
viewModelScope.launch {
database.itemDao().softDeleteItem(itemId, System.currentTimeMillis())
val ts = System.currentTimeMillis()
database.withTransaction {
database.itemDao().softDeleteItem(itemId, ts)
database.listDao().getListById(listId)?.let { l ->
database.listDao().updateList(l.copy(updatedAt = ts))
}
}
performSync()
}
}
@@ -263,8 +296,13 @@ class ListDetailViewModel(
fun reorderFields(reorderedFields: List<Field>) {
viewModelScope.launch {
val timestamp = System.currentTimeMillis()
// Use DAO-level transaction for clarity
database.fieldDao().reorderFieldsInTransaction(reorderedFields, timestamp)
// Reorder within a transaction and bump list.updatedAt
database.withTransaction {
database.fieldDao().reorderFieldsInTransaction(reorderedFields, timestamp)
database.listDao().getListById(listId)?.let { l ->
database.listDao().updateList(l.copy(updatedAt = timestamp))
}
}
performSync()
}
}