Replace status_id with in_progress boolean across mobile apps

- Remove TaskStatus model and status_id foreign key references
- Add in_progress boolean field to task models and forms
- Update TaskApi to use dedicated POST endpoints for task actions:
  - POST /tasks/:id/cancel/ instead of PATCH with is_cancelled
  - POST /tasks/:id/uncancel/
  - POST /tasks/:id/archive/
  - POST /tasks/:id/unarchive/
- Fix iOS TaskViewModel to use error-first pattern for Kotlin-Swift
  generic type bridging issues
- Update iOS callback signatures to pass full TaskResponse instead
  of just taskId to avoid stale closure lookups
- Add in_progress localization strings
- Update widget preview data to use inProgress boolean

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-08 20:47:59 -06:00
parent a067228597
commit 4a04aff1e6
33 changed files with 314 additions and 376 deletions

View File

@@ -52,7 +52,6 @@ import com.example.casera.models.TaskCategory
import com.example.casera.models.TaskDetail
import com.example.casera.models.TaskFrequency
import com.example.casera.models.TaskPriority
import com.example.casera.models.TaskStatus
import com.example.casera.network.ApiResult
import com.example.casera.network.AuthApi
import com.example.casera.data.DataManager
@@ -413,8 +412,7 @@ fun App(
frequencyName = task.frequency?.name ?: "",
priorityId = task.priority?.id ?: 0,
priorityName = task.priority?.name ?: "",
statusId = task.status?.id,
statusName = task.status?.name,
inProgress = task.inProgress,
dueDate = task.dueDate,
estimatedCost = task.estimatedCost?.toString(),
createdAt = task.createdAt,
@@ -576,8 +574,7 @@ fun App(
frequencyName = task.frequency?.name ?: "",
priorityId = task.priority?.id ?: 0,
priorityName = task.priority?.name ?: "",
statusId = task.status?.id,
statusName = task.status?.name,
inProgress = task.inProgress,
dueDate = task.dueDate,
estimatedCost = task.estimatedCost?.toString(),
createdAt = task.createdAt,
@@ -607,9 +604,7 @@ fun App(
days = null
),
priority = TaskPriority(id = route.priorityId, name = route.priorityName),
status = route.statusId?.let {
TaskStatus(id = it, name = route.statusName ?: "")
},
inProgress = route.inProgress,
dueDate = route.dueDate,
estimatedCost = route.estimatedCost?.toDoubleOrNull(),
createdAt = route.createdAt,

View File

@@ -158,9 +158,6 @@ object DataManager {
private val _taskPriorities = MutableStateFlow<List<TaskPriority>>(emptyList())
val taskPriorities: StateFlow<List<TaskPriority>> = _taskPriorities.asStateFlow()
private val _taskStatuses = MutableStateFlow<List<TaskStatus>>(emptyList())
val taskStatuses: StateFlow<List<TaskStatus>> = _taskStatuses.asStateFlow()
private val _taskCategories = MutableStateFlow<List<TaskCategory>>(emptyList())
val taskCategories: StateFlow<List<TaskCategory>> = _taskCategories.asStateFlow()
@@ -185,9 +182,6 @@ object DataManager {
private val _taskPrioritiesMap = MutableStateFlow<Map<Int, TaskPriority>>(emptyMap())
val taskPrioritiesMap: StateFlow<Map<Int, TaskPriority>> = _taskPrioritiesMap.asStateFlow()
private val _taskStatusesMap = MutableStateFlow<Map<Int, TaskStatus>>(emptyMap())
val taskStatusesMap: StateFlow<Map<Int, TaskStatus>> = _taskStatusesMap.asStateFlow()
private val _taskCategoriesMap = MutableStateFlow<Map<Int, TaskCategory>>(emptyMap())
val taskCategoriesMap: StateFlow<Map<Int, TaskCategory>> = _taskCategoriesMap.asStateFlow()
@@ -247,7 +241,6 @@ object DataManager {
fun getResidenceType(id: Int?): ResidenceType? = id?.let { _residenceTypesMap.value[it] }
fun getTaskFrequency(id: Int?): TaskFrequency? = id?.let { _taskFrequenciesMap.value[it] }
fun getTaskPriority(id: Int?): TaskPriority? = id?.let { _taskPrioritiesMap.value[it] }
fun getTaskStatus(id: Int?): TaskStatus? = id?.let { _taskStatusesMap.value[it] }
fun getTaskCategory(id: Int?): TaskCategory? = id?.let { _taskCategoriesMap.value[it] }
fun getContractorSpecialty(id: Int?): ContractorSpecialty? = id?.let { _contractorSpecialtiesMap.value[it] }
@@ -533,12 +526,6 @@ object DataManager {
persistToDisk()
}
fun setTaskStatuses(statuses: List<TaskStatus>) {
_taskStatuses.value = statuses
_taskStatusesMap.value = statuses.associateBy { it.id }
persistToDisk()
}
fun setTaskCategories(categories: List<TaskCategory>) {
_taskCategories.value = categories
_taskCategoriesMap.value = categories.associateBy { it.id }
@@ -583,7 +570,6 @@ object DataManager {
setResidenceTypes(staticData.residenceTypes)
setTaskFrequencies(staticData.taskFrequencies)
setTaskPriorities(staticData.taskPriorities)
setTaskStatuses(staticData.taskStatuses)
setTaskCategories(staticData.taskCategories)
setContractorSpecialties(staticData.contractorSpecialties)
_lookupsInitialized.value = true
@@ -598,7 +584,6 @@ object DataManager {
setResidenceTypes(seededData.residenceTypes)
setTaskFrequencies(seededData.taskFrequencies)
setTaskPriorities(seededData.taskPriorities)
setTaskStatuses(seededData.taskStatuses)
setTaskCategories(seededData.taskCategories)
setContractorSpecialties(seededData.contractorSpecialties)
setTaskTemplatesGrouped(seededData.taskTemplates)
@@ -659,8 +644,6 @@ object DataManager {
_taskFrequenciesMap.value = emptyMap()
_taskPriorities.value = emptyList()
_taskPrioritiesMap.value = emptyMap()
_taskStatuses.value = emptyList()
_taskStatusesMap.value = emptyMap()
_taskCategories.value = emptyList()
_taskCategoriesMap.value = emptyMap()
_contractorSpecialties.value = emptyList()
@@ -756,9 +739,6 @@ object DataManager {
if (_taskPriorities.value.isNotEmpty()) {
manager.save(KEY_TASK_PRIORITIES, json.encodeToString(_taskPriorities.value))
}
if (_taskStatuses.value.isNotEmpty()) {
manager.save(KEY_TASK_STATUSES, json.encodeToString(_taskStatuses.value))
}
if (_taskCategories.value.isNotEmpty()) {
manager.save(KEY_TASK_CATEGORIES, json.encodeToString(_taskCategories.value))
}
@@ -832,12 +812,6 @@ object DataManager {
_taskPrioritiesMap.value = priorities.associateBy { it.id }
}
manager.load(KEY_TASK_STATUSES)?.let { data ->
val statuses = json.decodeFromString<List<TaskStatus>>(data)
_taskStatuses.value = statuses
_taskStatusesMap.value = statuses.associateBy { it.id }
}
manager.load(KEY_TASK_CATEGORIES)?.let { data ->
val categories = json.decodeFromString<List<TaskCategory>>(data)
_taskCategories.value = categories
@@ -874,7 +848,6 @@ object DataManager {
private const val KEY_RESIDENCE_TYPES = "dm_residence_types"
private const val KEY_TASK_FREQUENCIES = "dm_task_frequencies"
private const val KEY_TASK_PRIORITIES = "dm_task_priorities"
private const val KEY_TASK_STATUSES = "dm_task_statuses"
private const val KEY_TASK_CATEGORIES = "dm_task_categories"
private const val KEY_CONTRACTOR_SPECIALTIES = "dm_contractor_specialties"
private const val KEY_TASK_TEMPLATES_GROUPED = "dm_task_templates_grouped"

View File

@@ -40,8 +40,7 @@ data class TaskResponse(
val category: TaskCategory? = null,
@SerialName("priority_id") val priorityId: Int? = null,
val priority: TaskPriority? = null,
@SerialName("status_id") val statusId: Int? = null,
val status: TaskStatus? = null,
@SerialName("in_progress") val inProgress: Boolean = false,
@SerialName("frequency_id") val frequencyId: Int? = null,
val frequency: TaskFrequency? = null,
@SerialName("due_date") val dueDate: String? = null,
@@ -74,11 +73,10 @@ data class TaskResponse(
val frequencyDaySpan: Int? get() = frequency?.days
val priorityName: String? get() = priority?.name
val priorityDisplayName: String? get() = priority?.displayName
val statusName: String? get() = status?.name
// Fields that don't exist in Go API - return null/default
val nextScheduledDate: String? get() = null // Would need calculation based on frequency
val showCompletedButton: Boolean get() = status?.name?.lowercase() != "completed"
val showCompletedButton: Boolean get() = true // Always show complete button since status is now just in_progress boolean
val daySpan: Int? get() = frequency?.days
val notifyDays: Int? get() = null // Not in Go API
}
@@ -119,7 +117,7 @@ data class TaskCreateRequest(
val description: String? = null,
@SerialName("category_id") val categoryId: Int? = null,
@SerialName("priority_id") val priorityId: Int? = null,
@SerialName("status_id") val statusId: Int? = null,
@SerialName("in_progress") val inProgress: Boolean = false,
@SerialName("frequency_id") val frequencyId: Int? = null,
@SerialName("assigned_to_id") val assignedToId: Int? = null,
@SerialName("due_date") val dueDate: String? = null,
@@ -137,7 +135,7 @@ data class TaskUpdateRequest(
val description: String? = null,
@SerialName("category_id") val categoryId: Int? = null,
@SerialName("priority_id") val priorityId: Int? = null,
@SerialName("status_id") val statusId: Int? = null,
@SerialName("in_progress") val inProgress: Boolean? = null,
@SerialName("frequency_id") val frequencyId: Int? = null,
@SerialName("assigned_to_id") val assignedToId: Int? = null,
@SerialName("due_date") val dueDate: String? = null,
@@ -176,7 +174,8 @@ data class TaskColumn(
data class TaskColumnsResponse(
val columns: List<TaskColumn>,
@SerialName("days_threshold") val daysThreshold: Int,
@SerialName("residence_id") val residenceId: String
@SerialName("residence_id") val residenceId: String,
val summary: TotalSummary? = null
)
/**
@@ -184,7 +183,7 @@ data class TaskColumnsResponse(
*/
@Serializable
data class TaskPatchRequest(
@SerialName("status_id") val status: Int? = null,
@SerialName("in_progress") val inProgress: Boolean? = null,
@SerialName("is_archived") val archived: Boolean? = null,
@SerialName("is_cancelled") val cancelled: Boolean? = null
)

View File

@@ -44,22 +44,6 @@ data class TaskPriority(
get() = name
}
/**
* Task status lookup - matching Go API TaskStatusResponse
*/
@Serializable
data class TaskStatus(
val id: Int,
val name: String,
val description: String = "",
val color: String = "",
@SerialName("display_order") val displayOrder: Int = 0
) {
// Helper for display
val displayName: String
get() = name
}
/**
* Task category lookup - matching Go API TaskCategoryResponse
*/
@@ -104,7 +88,6 @@ data class StaticDataResponse(
@SerialName("residence_types") val residenceTypes: List<ResidenceType>,
@SerialName("task_frequencies") val taskFrequencies: List<TaskFrequency>,
@SerialName("task_priorities") val taskPriorities: List<TaskPriority>,
@SerialName("task_statuses") val taskStatuses: List<TaskStatus>,
@SerialName("task_categories") val taskCategories: List<TaskCategory>,
@SerialName("contractor_specialties") val contractorSpecialties: List<ContractorSpecialty>
)
@@ -119,7 +102,6 @@ data class SeededDataResponse(
@SerialName("task_categories") val taskCategories: List<TaskCategory>,
@SerialName("task_priorities") val taskPriorities: List<TaskPriority>,
@SerialName("task_frequencies") val taskFrequencies: List<TaskFrequency>,
@SerialName("task_statuses") val taskStatuses: List<TaskStatus>,
@SerialName("contractor_specialties") val contractorSpecialties: List<ContractorSpecialty>,
@SerialName("task_templates") val taskTemplates: TaskTemplatesGroupedResponse
)
@@ -145,12 +127,6 @@ data class TaskPriorityResponse(
val results: List<TaskPriority>
)
@Serializable
data class TaskStatusResponse(
val count: Int,
val results: List<TaskStatus>
)
@Serializable
data class TaskCategoryResponse(
val count: Int,

View File

@@ -60,8 +60,7 @@ data class EditTaskRoute(
val frequencyName: String,
val priorityId: Int,
val priorityName: String,
val statusId: Int?,
val statusName: String?,
val inProgress: Boolean,
val dueDate: String?,
val estimatedCost: String?,
val createdAt: String,

View File

@@ -271,27 +271,6 @@ object APILayer {
return result
}
/**
* Get task statuses from DataManager. If cache is empty, fetch from API.
*/
suspend fun getTaskStatuses(forceRefresh: Boolean = false): ApiResult<List<TaskStatus>> {
if (!forceRefresh) {
val cached = DataManager.taskStatuses.value
if (cached.isNotEmpty()) {
return ApiResult.Success(cached)
}
}
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
val result = lookupsApi.getTaskStatuses(token)
if (result is ApiResult.Success) {
DataManager.setTaskStatuses(result.data)
}
return result
}
/**
* Get task categories from DataManager. If cache is empty, fetch from API.
*/
@@ -594,22 +573,9 @@ object APILayer {
}
}
/**
* Get status ID by name from DataManager.
*/
private fun getStatusIdByName(name: String): Int? {
return DataManager.taskStatuses.value.find {
it.name.equals(name, ignoreCase = true)
}?.id
}
suspend fun cancelTask(taskId: Int): ApiResult<TaskResponse> {
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
val cancelledStatusId = getStatusIdByName("cancelled")
?: return ApiResult.Error("Cancelled status not found in cache")
val result = taskApi.cancelTask(token, taskId, cancelledStatusId)
val result = taskApi.cancelTask(token, taskId)
if (result is ApiResult.Success) {
DataManager.setTotalSummary(result.data.summary)
@@ -625,11 +591,7 @@ object APILayer {
suspend fun uncancelTask(taskId: Int): ApiResult<TaskResponse> {
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
val pendingStatusId = getStatusIdByName("pending")
?: return ApiResult.Error("Pending status not found in cache")
val result = taskApi.uncancelTask(token, taskId, pendingStatusId)
val result = taskApi.uncancelTask(token, taskId)
if (result is ApiResult.Success) {
DataManager.setTotalSummary(result.data.summary)
@@ -645,12 +607,23 @@ object APILayer {
suspend fun markInProgress(taskId: Int): ApiResult<TaskResponse> {
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
val result = taskApi.markInProgress(token, taskId)
val inProgressStatusId = getStatusIdByName("in progress")
?: getStatusIdByName("In Progress")
?: return ApiResult.Error("In Progress status not found in cache")
if (result is ApiResult.Success) {
DataManager.setTotalSummary(result.data.summary)
DataManager.updateTask(result.data.data)
return ApiResult.Success(result.data.data)
}
val result = taskApi.markInProgress(token, taskId, inProgressStatusId)
return when (result) {
is ApiResult.Error -> result
else -> ApiResult.Error("Unknown error")
}
}
suspend fun clearInProgress(taskId: Int): ApiResult<TaskResponse> {
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
val result = taskApi.clearInProgress(token, taskId)
if (result is ApiResult.Success) {
DataManager.setTotalSummary(result.data.summary)

View File

@@ -81,22 +81,6 @@ class LookupsApi(private val client: HttpClient = ApiClient.httpClient) {
}
}
suspend fun getTaskStatuses(token: String): ApiResult<List<TaskStatus>> {
return try {
val response = client.get("$baseUrl/task-statuses/") {
header("Authorization", "Token $token")
}
if (response.status.isSuccess()) {
ApiResult.Success(response.body())
} else {
ApiResult.Error("Failed to fetch task statuses", response.status.value)
}
} catch (e: Exception) {
ApiResult.Error(e.message ?: "Unknown error occurred")
}
}
suspend fun getTaskCategories(token: String): ApiResult<List<TaskCategory>> {
return try {
val response = client.get("$baseUrl/task-categories/") {

View File

@@ -149,26 +149,57 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
}
// Convenience methods for common task actions
// These use PATCH internally to update task status/archived state
// These use dedicated POST endpoints for state changes
suspend fun cancelTask(token: String, id: Int, cancelledStatusId: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
return patchTask(token, id, TaskPatchRequest(status = cancelledStatusId))
suspend fun cancelTask(token: String, id: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
return postTaskAction(token, id, "cancel")
}
suspend fun uncancelTask(token: String, id: Int, pendingStatusId: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
return patchTask(token, id, TaskPatchRequest(status = pendingStatusId))
suspend fun uncancelTask(token: String, id: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
return postTaskAction(token, id, "uncancel")
}
suspend fun markInProgress(token: String, id: Int, inProgressStatusId: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
return patchTask(token, id, TaskPatchRequest(status = inProgressStatusId))
suspend fun markInProgress(token: String, id: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
return patchTask(token, id, TaskPatchRequest(inProgress = true))
}
suspend fun clearInProgress(token: String, id: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
return patchTask(token, id, TaskPatchRequest(inProgress = false))
}
suspend fun archiveTask(token: String, id: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
return patchTask(token, id, TaskPatchRequest(archived = true))
return postTaskAction(token, id, "archive")
}
suspend fun unarchiveTask(token: String, id: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
return patchTask(token, id, TaskPatchRequest(archived = false))
return postTaskAction(token, id, "unarchive")
}
/**
* Helper for POST task action endpoints (cancel, uncancel, archive, unarchive)
*/
private suspend fun postTaskAction(token: String, id: Int, action: String): ApiResult<WithSummaryResponse<TaskResponse>> {
return try {
val response = client.post("$baseUrl/tasks/$id/$action/") {
header("Authorization", "Token $token")
contentType(ContentType.Application.Json)
}
when (response.status) {
HttpStatusCode.OK -> {
val data = response.body<WithSummaryResponse<TaskResponse>>()
ApiResult.Success(data)
}
HttpStatusCode.NotFound -> ApiResult.Error("Task not found", 404)
HttpStatusCode.Forbidden -> ApiResult.Error("Access denied", 403)
HttpStatusCode.BadRequest -> {
val errorBody = response.body<String>()
ApiResult.Error(errorBody, 400)
}
else -> ApiResult.Error("Task $action failed: ${response.status}", response.status.value)
}
} catch (e: Exception) {
ApiResult.Error(e.message ?: "Unknown error occurred")
}
}
/**

View File

@@ -22,7 +22,6 @@ object LookupsRepository {
val residenceTypes: StateFlow<List<ResidenceType>> = DataManager.residenceTypes
val taskFrequencies: StateFlow<List<TaskFrequency>> = DataManager.taskFrequencies
val taskPriorities: StateFlow<List<TaskPriority>> = DataManager.taskPriorities
val taskStatuses: StateFlow<List<TaskStatus>> = DataManager.taskStatuses
val taskCategories: StateFlow<List<TaskCategory>> = DataManager.taskCategories
val contractorSpecialties: StateFlow<List<ContractorSpecialty>> = DataManager.contractorSpecialties
val isInitialized: StateFlow<Boolean> = DataManager.lookupsInitialized

View File

@@ -449,7 +449,7 @@ fun AddTaskDialog(
categoryId = if (category.id > 0) category.id else null,
frequencyId = if (frequency.id > 0) frequency.id else null,
priorityId = if (priority.id > 0) priority.id else null,
statusId = null,
inProgress = false,
dueDate = dueDate,
estimatedCost = estimatedCost.ifBlank { null }?.toDoubleOrNull()
)

View File

@@ -18,7 +18,6 @@ import com.example.casera.models.TaskDetail
import com.example.casera.models.TaskCategory
import com.example.casera.models.TaskPriority
import com.example.casera.models.TaskFrequency
import com.example.casera.models.TaskStatus
import com.example.casera.models.TaskCompletion
import com.example.casera.util.DateUtils
import org.jetbrains.compose.ui.tooling.preview.Preview
@@ -108,21 +107,15 @@ fun TaskCard(
)
}
// Status badge with semantic colors
if (task.status != null) {
val statusColor = when (task.status.name.lowercase()) {
"completed" -> MaterialTheme.colorScheme.secondary
"in_progress" -> MaterialTheme.colorScheme.tertiary
"pending" -> MaterialTheme.colorScheme.tertiary
"cancelled" -> MaterialTheme.colorScheme.onSurfaceVariant
else -> MaterialTheme.colorScheme.onSurfaceVariant
}
// In Progress badge
if (task.inProgress) {
val statusColor = MaterialTheme.colorScheme.tertiary
Surface(
color = statusColor.copy(alpha = 0.15f),
shape = RoundedCornerShape(12.dp)
) {
Text(
text = task.status.name.replace("_", " ").uppercase(),
text = "IN PROGRESS",
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
style = MaterialTheme.typography.labelSmall,
color = statusColor
@@ -604,7 +597,7 @@ fun TaskCardPreview() {
frequency = TaskFrequency(
id = 1, name = "monthly", days = 30
),
status = TaskStatus(id = 1, name = "pending"),
inProgress = false,
dueDate = "2024-12-15",
estimatedCost = 150.00,
createdAt = "2024-01-01T00:00:00Z",

View File

@@ -33,20 +33,18 @@ fun EditTaskScreen(
var selectedCategory by remember { mutableStateOf<TaskCategory?>(task.category) }
var selectedFrequency by remember { mutableStateOf<TaskFrequency?>(task.frequency) }
var selectedPriority by remember { mutableStateOf<TaskPriority?>(task.priority) }
var selectedStatus by remember { mutableStateOf<TaskStatus?>(task.status) }
var inProgress by remember { mutableStateOf(task.inProgress) }
var dueDate by remember { mutableStateOf(task.dueDate ?: "") }
var estimatedCost by remember { mutableStateOf(task.estimatedCost?.toString() ?: "") }
var categoryExpanded by remember { mutableStateOf(false) }
var frequencyExpanded by remember { mutableStateOf(false) }
var priorityExpanded by remember { mutableStateOf(false) }
var statusExpanded by remember { mutableStateOf(false) }
val updateTaskState by viewModel.updateTaskState.collectAsState()
val categories by LookupsRepository.taskCategories.collectAsState()
val frequencies by LookupsRepository.taskFrequencies.collectAsState()
val priorities by LookupsRepository.taskPriorities.collectAsState()
val statuses by LookupsRepository.taskStatuses.collectAsState()
// Validation errors
var titleError by remember { mutableStateOf("") }
@@ -235,36 +233,20 @@ fun EditTaskScreen(
}
}
// Status dropdown
ExposedDropdownMenuBox(
expanded = statusExpanded,
onExpandedChange = { statusExpanded = it }
// In Progress toggle
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
OutlinedTextField(
value = selectedStatus?.name?.replaceFirstChar { it.uppercase() } ?: "",
onValueChange = {},
readOnly = true,
label = { Text(stringResource(Res.string.tasks_status_label)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = statusExpanded) },
modifier = Modifier
.fillMaxWidth()
.menuAnchor(),
enabled = statuses.isNotEmpty()
Text(
text = stringResource(Res.string.tasks_in_progress_label),
style = MaterialTheme.typography.bodyLarge
)
Switch(
checked = inProgress,
onCheckedChange = { inProgress = it }
)
ExposedDropdownMenu(
expanded = statusExpanded,
onDismissRequest = { statusExpanded = false }
) {
statuses.forEach { status ->
DropdownMenuItem(
text = { Text(status.name.replaceFirstChar { it.uppercase() }) },
onClick = {
selectedStatus = status
statusExpanded = false
}
)
}
}
}
OutlinedTextField(
@@ -301,8 +283,7 @@ fun EditTaskScreen(
Button(
onClick = {
if (validateForm() && selectedCategory != null &&
selectedFrequency != null && selectedPriority != null &&
selectedStatus != null) {
selectedFrequency != null && selectedPriority != null) {
viewModel.updateTask(
taskId = task.id,
request = TaskCreateRequest(
@@ -312,7 +293,7 @@ fun EditTaskScreen(
categoryId = selectedCategory!!.id,
frequencyId = selectedFrequency!!.id,
priorityId = selectedPriority!!.id,
statusId = selectedStatus!!.id,
inProgress = inProgress,
dueDate = dueDate,
estimatedCost = estimatedCost.ifBlank { null }?.toDoubleOrNull()
)
@@ -321,8 +302,7 @@ fun EditTaskScreen(
},
modifier = Modifier.fillMaxWidth(),
enabled = validateForm() && selectedCategory != null &&
selectedFrequency != null && selectedPriority != null &&
selectedStatus != null
selectedFrequency != null && selectedPriority != null
) {
if (updateTaskState is ApiResult.Loading) {
CircularProgressIndicator(

View File

@@ -346,7 +346,7 @@ fun OnboardingFirstTaskContent(
description = null,
categoryId = categoryId,
priorityId = null,
statusId = null,
inProgress = false,
frequencyId = frequencyId,
assignedToId = null,
dueDate = today,

View File

@@ -21,7 +21,6 @@ class LookupsViewModel : ViewModel() {
val residenceTypes: StateFlow<List<ResidenceType>> = DataManager.residenceTypes
val taskFrequencies: StateFlow<List<TaskFrequency>> = DataManager.taskFrequencies
val taskPriorities: StateFlow<List<TaskPriority>> = DataManager.taskPriorities
val taskStatuses: StateFlow<List<TaskStatus>> = DataManager.taskStatuses
val taskCategories: StateFlow<List<TaskCategory>> = DataManager.taskCategories
val contractorSpecialties: StateFlow<List<ContractorSpecialty>> = DataManager.contractorSpecialties
@@ -35,9 +34,6 @@ class LookupsViewModel : ViewModel() {
private val _taskPrioritiesState = MutableStateFlow<ApiResult<List<TaskPriority>>>(ApiResult.Idle)
val taskPrioritiesState: StateFlow<ApiResult<List<TaskPriority>>> = _taskPrioritiesState
private val _taskStatusesState = MutableStateFlow<ApiResult<List<TaskStatus>>>(ApiResult.Idle)
val taskStatusesState: StateFlow<ApiResult<List<TaskStatus>>> = _taskStatusesState
private val _taskCategoriesState = MutableStateFlow<ApiResult<List<TaskCategory>>>(ApiResult.Idle)
val taskCategoriesState: StateFlow<ApiResult<List<TaskCategory>>> = _taskCategoriesState
@@ -80,19 +76,6 @@ class LookupsViewModel : ViewModel() {
}
}
fun loadTaskStatuses() {
viewModelScope.launch {
val cached = DataManager.taskStatuses.value
if (cached.isNotEmpty()) {
_taskStatusesState.value = ApiResult.Success(cached)
return@launch
}
_taskStatusesState.value = ApiResult.Loading
val result = APILayer.getTaskStatuses()
_taskStatusesState.value = result
}
}
fun loadTaskCategories() {
viewModelScope.launch {
val cached = DataManager.taskCategories.value
@@ -111,7 +94,6 @@ class LookupsViewModel : ViewModel() {
loadResidenceTypes()
loadTaskFrequencies()
loadTaskPriorities()
loadTaskStatuses()
loadTaskCategories()
}
}