feat: filter out deleted fields in ListDetailScreen and webRoutes for improved data integrity
This commit is contained in:
@@ -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()
|
||||
|
||||
+28
-6
@@ -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()),
|
||||
) {
|
||||
|
||||
+57
-19
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user