Remove API pagination and fix contractor UI issues
- Remove pagination from all Django REST Framework endpoints - Update Kotlin API clients to return direct lists instead of paginated responses - Update iOS ViewModels to handle direct list responses - Remove ContractorListResponse, DocumentListResponse, and PaginatedResponse models - Fix contractor form specialty selector loading with improved DataCache access - Fix contractor sheet presentation to use full screen (.presentationDetents([.large])) - Improve UI test scrolling to handle lists of any size with smart end detection 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -154,8 +154,8 @@ class DataPrefetchManager {
|
||||
search = null
|
||||
)
|
||||
if (result is ApiResult.Success) {
|
||||
DataCache.updateDocuments(result.data.results)
|
||||
println("DataPrefetchManager: Cached ${result.data.results.size} documents")
|
||||
DataCache.updateDocuments(result.data)
|
||||
println("DataPrefetchManager: Cached ${result.data.size} documents")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("DataPrefetchManager: Error fetching documents: ${e.message}")
|
||||
@@ -173,9 +173,9 @@ class DataPrefetchManager {
|
||||
search = null
|
||||
)
|
||||
if (result is ApiResult.Success) {
|
||||
// ContractorListResponse.results is List<ContractorSummary>, not List<Contractor>
|
||||
// API returns List<ContractorSummary>, not List<Contractor>
|
||||
// Skip caching for now - full Contractor objects will be cached when fetched individually
|
||||
println("DataPrefetchManager: Fetched ${result.data.results.size} contractor summaries")
|
||||
println("DataPrefetchManager: Fetched ${result.data.size} contractor summaries")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("DataPrefetchManager: Error fetching contractors: ${e.message}")
|
||||
|
||||
@@ -79,10 +79,5 @@ data class ContractorSummary(
|
||||
@SerialName("task_count") val taskCount: Int = 0
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ContractorListResponse(
|
||||
val count: Int,
|
||||
val next: String? = null,
|
||||
val previous: String? = null,
|
||||
val results: List<ContractorSummary>
|
||||
)
|
||||
// Removed: ContractorListResponse - no longer using paginated responses
|
||||
// API now returns List<ContractorSummary> directly
|
||||
|
||||
@@ -107,13 +107,8 @@ data class DocumentUpdateRequest(
|
||||
@SerialName("is_active") val isActive: Boolean? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DocumentListResponse(
|
||||
val count: Int,
|
||||
val next: String? = null,
|
||||
val previous: String? = null,
|
||||
val results: List<Document>
|
||||
)
|
||||
// Removed: DocumentListResponse - no longer using paginated responses
|
||||
// API now returns List<Document> directly
|
||||
|
||||
// Document type choices
|
||||
enum class DocumentType(val value: String, val displayName: String) {
|
||||
|
||||
@@ -513,7 +513,7 @@ object APILayer {
|
||||
tags: String? = null,
|
||||
search: String? = null,
|
||||
forceRefresh: Boolean = false
|
||||
): ApiResult<DocumentListResponse> {
|
||||
): ApiResult<List<Document>> {
|
||||
val hasFilters = residenceId != null || documentType != null || category != null ||
|
||||
contractorId != null || isActive != null || expiringSoon != null ||
|
||||
tags != null || search != null
|
||||
@@ -522,10 +522,7 @@ object APILayer {
|
||||
if (!forceRefresh && !hasFilters) {
|
||||
val cached = DataCache.documents.value
|
||||
if (cached.isNotEmpty()) {
|
||||
return ApiResult.Success(DocumentListResponse(
|
||||
count = cached.size,
|
||||
results = cached
|
||||
))
|
||||
return ApiResult.Success(cached)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,7 +535,7 @@ object APILayer {
|
||||
|
||||
// Update cache on success if no filters
|
||||
if (result is ApiResult.Success && !hasFilters) {
|
||||
DataCache.updateDocuments(result.data.results)
|
||||
DataCache.updateDocuments(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -688,10 +685,10 @@ object APILayer {
|
||||
isActive: Boolean? = null,
|
||||
search: String? = null,
|
||||
forceRefresh: Boolean = false
|
||||
): ApiResult<ContractorListResponse> {
|
||||
): ApiResult<List<ContractorSummary>> {
|
||||
val hasFilters = specialty != null || isFavorite != null || isActive != null || search != null
|
||||
|
||||
// Note: Cannot use cache here because ContractorListResponse expects List<ContractorSummary>
|
||||
// Note: Cannot use cache here because API returns List<ContractorSummary>
|
||||
// but DataCache stores List<Contractor>. Cache is only used for individual contractor lookups.
|
||||
|
||||
// Fetch from API
|
||||
@@ -700,7 +697,7 @@ object APILayer {
|
||||
|
||||
// Update cache on success if no filters
|
||||
if (result is ApiResult.Success && !hasFilters) {
|
||||
// ContractorListResponse.results is List<ContractorSummary>, but we need List<Contractor>
|
||||
// API returns List<ContractorSummary>, but we need List<Contractor> for cache
|
||||
// For now, we'll skip caching from this endpoint since it returns summaries
|
||||
// Cache will be populated from getContractor() or create/update operations
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class ContractorApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
isFavorite: Boolean? = null,
|
||||
isActive: Boolean? = null,
|
||||
search: String? = null
|
||||
): ApiResult<ContractorListResponse> {
|
||||
): ApiResult<List<ContractorSummary>> {
|
||||
return try {
|
||||
val response = client.get("$baseUrl/contractors/") {
|
||||
header("Authorization", "Token $token")
|
||||
|
||||
@@ -21,7 +21,7 @@ class DocumentApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
expiringSoon: Int? = null,
|
||||
tags: String? = null,
|
||||
search: String? = null
|
||||
): ApiResult<DocumentListResponse> {
|
||||
): ApiResult<List<Document>> {
|
||||
return try {
|
||||
val response = client.get("$baseUrl/documents/") {
|
||||
header("Authorization", "Token $token")
|
||||
|
||||
@@ -112,8 +112,7 @@ class LookupsApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
val data: PaginatedResponse<CustomTask> = response.body()
|
||||
ApiResult.Success(data.results)
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to fetch tasks", response.status.value)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ class ResidenceApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
val data: PaginatedResponse<Residence> = response.body()
|
||||
ApiResult.Success(data.results)
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to fetch residences", response.status.value)
|
||||
}
|
||||
@@ -245,10 +244,5 @@ data class GenerateReportResponse(
|
||||
val recipient_email: String
|
||||
)
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
data class PaginatedResponse<T>(
|
||||
val count: Int,
|
||||
val next: String?,
|
||||
val previous: String?,
|
||||
val results: List<T>
|
||||
)
|
||||
// Removed: PaginatedResponse - no longer using paginated responses
|
||||
// All API endpoints now return direct lists instead of paginated responses
|
||||
|
||||
@@ -16,8 +16,7 @@ class TaskCompletionApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
val data: PaginatedResponse<TaskCompletion> = response.body()
|
||||
ApiResult.Success(data.results)
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to fetch completions", response.status.value)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ fun CompleteTaskDialog(
|
||||
// Contractor list
|
||||
when (val state = contractorsState) {
|
||||
is ApiResult.Success -> {
|
||||
state.data.results.forEach { contractor ->
|
||||
state.data.forEach { contractor ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Column {
|
||||
|
||||
@@ -13,13 +13,13 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mycrib.shared.models.DocumentListResponse
|
||||
import com.mycrib.shared.models.Document
|
||||
import com.mycrib.shared.network.ApiResult
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DocumentsTabContent(
|
||||
state: ApiResult<DocumentListResponse>,
|
||||
state: ApiResult<List<Document>>,
|
||||
isWarrantyTab: Boolean,
|
||||
onDocumentClick: (Int) -> Unit,
|
||||
onRetry: () -> Unit
|
||||
@@ -42,7 +42,7 @@ fun DocumentsTabContent(
|
||||
}
|
||||
}
|
||||
is ApiResult.Success -> {
|
||||
val documents = state.data.results
|
||||
val documents = state.data
|
||||
if (documents.isEmpty()) {
|
||||
EmptyState(
|
||||
icon = if (isWarrantyTab) Icons.Default.ReceiptLong else Icons.Default.Description,
|
||||
|
||||
@@ -246,7 +246,7 @@ fun ContractorsScreen(
|
||||
}
|
||||
}
|
||||
) { state ->
|
||||
val contractors = state.results
|
||||
val contractors = state
|
||||
|
||||
if (contractors.isEmpty()) {
|
||||
Box(
|
||||
|
||||
@@ -11,8 +11,8 @@ import kotlinx.coroutines.launch
|
||||
|
||||
class ContractorViewModel : ViewModel() {
|
||||
|
||||
private val _contractorsState = MutableStateFlow<ApiResult<ContractorListResponse>>(ApiResult.Idle)
|
||||
val contractorsState: StateFlow<ApiResult<ContractorListResponse>> = _contractorsState
|
||||
private val _contractorsState = MutableStateFlow<ApiResult<List<ContractorSummary>>>(ApiResult.Idle)
|
||||
val contractorsState: StateFlow<ApiResult<List<ContractorSummary>>> = _contractorsState
|
||||
|
||||
private val _contractorDetailState = MutableStateFlow<ApiResult<Contractor>>(ApiResult.Idle)
|
||||
val contractorDetailState: StateFlow<ApiResult<Contractor>> = _contractorDetailState
|
||||
|
||||
@@ -12,8 +12,8 @@ import kotlinx.coroutines.launch
|
||||
|
||||
class DocumentViewModel : ViewModel() {
|
||||
|
||||
private val _documentsState = MutableStateFlow<ApiResult<DocumentListResponse>>(ApiResult.Idle)
|
||||
val documentsState: StateFlow<ApiResult<DocumentListResponse>> = _documentsState
|
||||
private val _documentsState = MutableStateFlow<ApiResult<List<Document>>>(ApiResult.Idle)
|
||||
val documentsState: StateFlow<ApiResult<List<Document>>> = _documentsState
|
||||
|
||||
private val _documentDetailState = MutableStateFlow<ApiResult<Document>>(ApiResult.Idle)
|
||||
val documentDetailState: StateFlow<ApiResult<Document>> = _documentDetailState
|
||||
|
||||
Reference in New Issue
Block a user