Update API layer to use TotalSummary from CRUD responses
Frontend changes: - Add generic WithSummaryResponse<T> model for CRUD responses - Update TaskApi, TaskCompletionApi, ResidenceApi return types - Update APILayer to extract summary from responses and call DataManager.setTotalSummary() - Replace refreshTasks() calls with DataManager.updateTask() for local cache updates - Remove redundant refreshMyResidences() calls - Remove unused helper methods (refreshTasks, refreshMyResidences, refreshSummary) - Add summary field to JoinResidenceResponse model This pairs with the backend changes to eliminate redundant network calls after CRUD operations - dashboard stats now update from the mutation response. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -165,7 +165,8 @@ data class JoinResidenceRequest(
|
||||
@Serializable
|
||||
data class JoinResidenceResponse(
|
||||
val message: String,
|
||||
val residence: ResidenceResponse
|
||||
val residence: ResidenceResponse,
|
||||
val summary: TotalSummary
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -181,6 +182,22 @@ data class TotalSummary(
|
||||
@SerialName("tasks_due_next_month") val tasksDueNextMonth: Int = 0
|
||||
)
|
||||
|
||||
/**
|
||||
* Generic wrapper for CRUD responses that include TotalSummary.
|
||||
* Used for Task and TaskCompletion operations to eliminate extra API calls
|
||||
* for updating dashboard stats.
|
||||
*
|
||||
* Usage examples:
|
||||
* - WithSummaryResponse<TaskResponse> for task CRUD
|
||||
* - WithSummaryResponse<TaskCompletionResponse> for completion CRUD
|
||||
* - WithSummaryResponse<String> for delete operations (data = "task deleted")
|
||||
*/
|
||||
@Serializable
|
||||
data class WithSummaryResponse<T>(
|
||||
val data: T,
|
||||
val summary: TotalSummary
|
||||
)
|
||||
|
||||
/**
|
||||
* My residences response - list of user's residences
|
||||
* Go API returns array directly, this wraps for consistency
|
||||
|
||||
@@ -424,42 +424,51 @@ object APILayer {
|
||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.createResidence(token, request)
|
||||
|
||||
// Update DataManager on success
|
||||
// Extract summary and update local cache
|
||||
if (result is ApiResult.Success) {
|
||||
DataManager.addResidence(result.data)
|
||||
// Also refresh my-residences to get updated list
|
||||
refreshMyResidences()
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
DataManager.addResidence(result.data.data)
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateResidence(id: Int, request: ResidenceCreateRequest): ApiResult<ResidenceResponse> {
|
||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.updateResidence(token, id, request)
|
||||
|
||||
// Update DataManager on success
|
||||
// Extract summary and update local cache
|
||||
if (result is ApiResult.Success) {
|
||||
DataManager.updateResidence(result.data)
|
||||
// Also refresh my-residences to get updated list
|
||||
refreshMyResidences()
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
DataManager.updateResidence(result.data.data)
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteResidence(id: Int): ApiResult<Unit> {
|
||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.deleteResidence(token, id)
|
||||
|
||||
// Update DataManager on success
|
||||
// Extract summary and update local cache
|
||||
if (result is ApiResult.Success) {
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
DataManager.removeResidence(id)
|
||||
// Also refresh my-residences to get updated list
|
||||
refreshMyResidences()
|
||||
return ApiResult.Success(Unit)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun generateTasksReport(residenceId: Int, email: String? = null): ApiResult<GenerateReportResponse> {
|
||||
@@ -471,9 +480,10 @@ object APILayer {
|
||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = residenceApi.joinWithCode(token, code)
|
||||
|
||||
// Refresh residences after joining
|
||||
// Extract summary and update local cache
|
||||
if (result is ApiResult.Success) {
|
||||
refreshMyResidences()
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
DataManager.addResidence(result.data.residence)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -552,24 +562,36 @@ object APILayer {
|
||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.createTask(token, request)
|
||||
|
||||
// Refresh tasks on success
|
||||
// Extract summary and update local cache with new task
|
||||
if (result is ApiResult.Success) {
|
||||
refreshTasks()
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
// Add the new task to the appropriate kanban column (uses kanbanColumn from response)
|
||||
DataManager.updateTask(result.data.data)
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateTask(id: Int, request: TaskCreateRequest): ApiResult<TaskResponse> {
|
||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.updateTask(token, id, request)
|
||||
|
||||
// Refresh tasks on success
|
||||
// Extract summary and update local cache with modified task
|
||||
if (result is ApiResult.Success) {
|
||||
refreshTasks()
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
// Update task in cache (handles column changes if due date changed)
|
||||
DataManager.updateTask(result.data.data)
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -590,10 +612,15 @@ object APILayer {
|
||||
val result = taskApi.cancelTask(token, taskId, cancelledStatusId)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataManager.updateTask(result.data)
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
DataManager.updateTask(result.data.data)
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uncancelTask(taskId: Int): ApiResult<TaskResponse> {
|
||||
@@ -605,10 +632,15 @@ object APILayer {
|
||||
val result = taskApi.uncancelTask(token, taskId, pendingStatusId)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataManager.updateTask(result.data)
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
DataManager.updateTask(result.data.data)
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun markInProgress(taskId: Int): ApiResult<TaskResponse> {
|
||||
@@ -621,10 +653,15 @@ object APILayer {
|
||||
val result = taskApi.markInProgress(token, taskId, inProgressStatusId)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataManager.updateTask(result.data)
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
DataManager.updateTask(result.data.data)
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun archiveTask(taskId: Int): ApiResult<TaskResponse> {
|
||||
@@ -632,10 +669,15 @@ object APILayer {
|
||||
val result = taskApi.archiveTask(token, taskId)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
DataManager.removeTask(taskId)
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unarchiveTask(taskId: Int): ApiResult<TaskResponse> {
|
||||
@@ -643,10 +685,15 @@ object APILayer {
|
||||
val result = taskApi.unarchiveTask(token, taskId)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataManager.updateTask(result.data)
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
DataManager.updateTask(result.data.data)
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createTaskCompletion(request: TaskCompletionCreateRequest): ApiResult<TaskCompletionResponse> {
|
||||
@@ -654,15 +701,19 @@ object APILayer {
|
||||
val result = taskCompletionApi.createCompletion(token, request)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
// Update summary from response - eliminates need for separate getSummary call
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
// The response includes the updated task, update it in DataManager
|
||||
result.data.updatedTask?.let { updatedTask ->
|
||||
result.data.data.updatedTask?.let { updatedTask ->
|
||||
DataManager.updateTask(updatedTask)
|
||||
}
|
||||
// Refresh my-residences to update per-residence overdueCount and summary
|
||||
refreshMyResidences()
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createTaskCompletionWithImages(
|
||||
@@ -674,15 +725,19 @@ object APILayer {
|
||||
val result = taskCompletionApi.createCompletionWithImages(token, request, images, imageFileNames)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
// Update summary from response - eliminates need for separate getSummary call
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
// The response includes the updated task, update it in DataManager
|
||||
result.data.updatedTask?.let { updatedTask ->
|
||||
result.data.data.updatedTask?.let { updatedTask ->
|
||||
DataManager.updateTask(updatedTask)
|
||||
}
|
||||
// Refresh my-residences to update per-residence overdueCount and summary
|
||||
refreshMyResidences()
|
||||
return ApiResult.Success(result.data.data)
|
||||
}
|
||||
|
||||
return result
|
||||
return when (result) {
|
||||
is ApiResult.Error -> result
|
||||
else -> ApiResult.Error("Unknown error")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1240,27 +1295,6 @@ object APILayer {
|
||||
|
||||
// ==================== Helper Methods ====================
|
||||
|
||||
/**
|
||||
* Refresh all tasks from API
|
||||
*/
|
||||
private suspend fun refreshTasks() {
|
||||
getTasks(forceRefresh = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh my-residences from API
|
||||
*/
|
||||
private suspend fun refreshMyResidences() {
|
||||
getMyResidences(forceRefresh = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh just the summary counts (lightweight)
|
||||
*/
|
||||
private suspend fun refreshSummary() {
|
||||
getSummary(forceRefresh = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch all data after login
|
||||
*/
|
||||
|
||||
@@ -41,7 +41,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createResidence(token: String, request: ResidenceCreateRequest): ApiResult<ResidenceResponse> {
|
||||
suspend fun createResidence(token: String, request: ResidenceCreateRequest): ApiResult<WithSummaryResponse<ResidenceResponse>> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/residences/") {
|
||||
header("Authorization", "Token $token")
|
||||
@@ -59,7 +59,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateResidence(token: String, id: Int, request: ResidenceCreateRequest): ApiResult<ResidenceResponse> {
|
||||
suspend fun updateResidence(token: String, id: Int, request: ResidenceCreateRequest): ApiResult<WithSummaryResponse<ResidenceResponse>> {
|
||||
return try {
|
||||
val response = client.put("$baseUrl/residences/$id/") {
|
||||
header("Authorization", "Token $token")
|
||||
@@ -77,14 +77,14 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteResidence(token: String, id: Int): ApiResult<Unit> {
|
||||
suspend fun deleteResidence(token: String, id: Int): ApiResult<WithSummaryResponse<String>> {
|
||||
return try {
|
||||
val response = client.delete("$baseUrl/residences/$id/") {
|
||||
header("Authorization", "Token $token")
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(Unit)
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to delete residence", response.status.value)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createTask(token: String, request: TaskCreateRequest): ApiResult<TaskResponse> {
|
||||
suspend fun createTask(token: String, request: TaskCreateRequest): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/tasks/") {
|
||||
header("Authorization", "Token $token")
|
||||
@@ -66,7 +66,7 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateTask(token: String, id: Int, request: TaskCreateRequest): ApiResult<TaskResponse> {
|
||||
suspend fun updateTask(token: String, id: Int, request: TaskCreateRequest): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||
return try {
|
||||
val response = client.put("$baseUrl/tasks/$id/") {
|
||||
header("Authorization", "Token $token")
|
||||
@@ -85,14 +85,14 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteTask(token: String, id: Int): ApiResult<Unit> {
|
||||
suspend fun deleteTask(token: String, id: Int): ApiResult<WithSummaryResponse<String>> {
|
||||
return try {
|
||||
val response = client.delete("$baseUrl/tasks/$id/") {
|
||||
header("Authorization", "Token $token")
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(Unit)
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
val errorMessage = ErrorParser.parseError(response)
|
||||
ApiResult.Error(errorMessage, response.status.value)
|
||||
@@ -127,12 +127,9 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
/**
|
||||
* Generic PATCH method for partial task updates.
|
||||
* Used for status changes and archive/unarchive operations.
|
||||
*
|
||||
* NOTE: The old custom action endpoints (cancel, uncancel, mark-in-progress,
|
||||
* archive, unarchive) have been REMOVED from the API.
|
||||
* All task updates now use PATCH /tasks/{id}/.
|
||||
* Returns TaskWithSummaryResponse to update dashboard stats in one call.
|
||||
*/
|
||||
suspend fun patchTask(token: String, id: Int, request: TaskPatchRequest): ApiResult<TaskResponse> {
|
||||
suspend fun patchTask(token: String, id: Int, request: TaskPatchRequest): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||
return try {
|
||||
val response = client.patch("$baseUrl/tasks/$id/") {
|
||||
header("Authorization", "Token $token")
|
||||
@@ -151,27 +148,26 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
// DEPRECATED: These methods now use PATCH internally.
|
||||
// They're kept for backward compatibility with existing ViewModel calls.
|
||||
// New code should use patchTask directly with status IDs from DataManager.
|
||||
// Convenience methods for common task actions
|
||||
// These use PATCH internally to update task status/archived state
|
||||
|
||||
suspend fun cancelTask(token: String, id: Int, cancelledStatusId: Int): ApiResult<TaskResponse> {
|
||||
suspend fun cancelTask(token: String, id: Int, cancelledStatusId: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||
return patchTask(token, id, TaskPatchRequest(status = cancelledStatusId))
|
||||
}
|
||||
|
||||
suspend fun uncancelTask(token: String, id: Int, pendingStatusId: Int): ApiResult<TaskResponse> {
|
||||
suspend fun uncancelTask(token: String, id: Int, pendingStatusId: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||
return patchTask(token, id, TaskPatchRequest(status = pendingStatusId))
|
||||
}
|
||||
|
||||
suspend fun markInProgress(token: String, id: Int, inProgressStatusId: Int): ApiResult<TaskResponse> {
|
||||
suspend fun markInProgress(token: String, id: Int, inProgressStatusId: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||
return patchTask(token, id, TaskPatchRequest(status = inProgressStatusId))
|
||||
}
|
||||
|
||||
suspend fun archiveTask(token: String, id: Int): ApiResult<TaskResponse> {
|
||||
suspend fun archiveTask(token: String, id: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||
return patchTask(token, id, TaskPatchRequest(archived = true))
|
||||
}
|
||||
|
||||
suspend fun unarchiveTask(token: String, id: Int): ApiResult<TaskResponse> {
|
||||
suspend fun unarchiveTask(token: String, id: Int): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||
return patchTask(token, id, TaskPatchRequest(archived = false))
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class TaskCompletionApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createCompletion(token: String, request: TaskCompletionCreateRequest): ApiResult<TaskCompletionResponse> {
|
||||
suspend fun createCompletion(token: String, request: TaskCompletionCreateRequest): ApiResult<WithSummaryResponse<TaskCompletionResponse>> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/task-completions/") {
|
||||
header("Authorization", "Token $token")
|
||||
@@ -77,14 +77,14 @@ class TaskCompletionApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteCompletion(token: String, id: Int): ApiResult<Unit> {
|
||||
suspend fun deleteCompletion(token: String, id: Int): ApiResult<WithSummaryResponse<String>> {
|
||||
return try {
|
||||
val response = client.delete("$baseUrl/task-completions/$id/") {
|
||||
header("Authorization", "Token $token")
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(Unit)
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to delete completion", response.status.value)
|
||||
}
|
||||
@@ -98,7 +98,7 @@ class TaskCompletionApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
request: TaskCompletionCreateRequest,
|
||||
images: List<ByteArray> = emptyList(),
|
||||
imageFileNames: List<String> = emptyList()
|
||||
): ApiResult<TaskCompletionResponse> {
|
||||
): ApiResult<WithSummaryResponse<TaskCompletionResponse>> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/task-completions/") {
|
||||
header("Authorization", "Token $token")
|
||||
|
||||
Reference in New Issue
Block a user