diff --git a/src/pages/Work.vue b/src/pages/Work.vue index 607072540..0aef1b278 100644 --- a/src/pages/Work.vue +++ b/src/pages/Work.vue @@ -982,7 +982,7 @@ import Worksite from '../models/Worksite'; import CaseHistory from '../components/work/CaseHistory.vue'; import { loadCaseImagesCached, - loadCasesCached, + loadCasesCachedWithPagination, loadUserLocations, } from '../utils/worksite'; import { averageGeolocation, getUserLocationLayer } from '../utils/map'; @@ -1256,7 +1256,7 @@ export default defineComponent({ async function getWorksites() { mapLoading.value = true; - const response = await loadCasesCached({ + const response = await loadCasesCachedWithPagination({ ...worksiteQuery.value, }); mapLoading.value = false; @@ -1274,7 +1274,7 @@ export default defineComponent({ async function getAllWorksites() { mapLoading.value = true; - const response = await loadCasesCached({ + const response = await loadCasesCachedWithPagination({ incident: currentIncidentId.value, }); mapLoading.value = false; diff --git a/src/pages/phone/PhoneSystem.vue b/src/pages/phone/PhoneSystem.vue index 2dd0bcc21..59e8ded55 100644 --- a/src/pages/phone/PhoneSystem.vue +++ b/src/pages/phone/PhoneSystem.vue @@ -540,7 +540,7 @@ import useDialogs from '../../hooks/useDialogs'; import useConnectFirst from '../../hooks/useConnectFirst'; import User from '../../models/User'; import WorksiteForm from '../../components/work/WorksiteForm.vue'; -import { loadCasesCached } from '@/utils/worksite'; +import { loadCasesCachedWithPagination } from '@/utils/worksite'; import { getErrorMessage } from '@/utils/errors'; import usePhoneService from '@/hooks/phone/usePhoneService'; import WorksiteTable from '@/components/work/WorksiteTable.vue'; @@ -1120,7 +1120,7 @@ export default defineComponent({ async function getWorksites() { mapLoading.value = true; - const response = await loadCasesCached({ + const response = await loadCasesCachedWithPagination({ incident: currentIncidentId.value, }); mapLoading.value = false; diff --git a/src/utils/dashboard.ts b/src/utils/dashboard.ts index f8d6b3b53..88683441c 100644 --- a/src/utils/dashboard.ts +++ b/src/utils/dashboard.ts @@ -5,7 +5,7 @@ import { getApiUrl } from '@/utils/helpers'; import Worksite from '@/models/Worksite'; import { getQueryString } from '@/utils/urls'; import InvitationRequest from '@/models/InvitationRequest'; -import { loadCasesCached } from '@/utils/worksite'; +import { loadCasesCachedWithPagination } from '@/utils/worksite'; import User from '@/models/User'; import Organization from '@/models/Organization'; @@ -73,7 +73,7 @@ async function getInvitationRequests() { } async function getWorksites(incident) { - const response = await loadCasesCached({ + const response = await loadCasesCachedWithPagination({ incident: incident, }); return response.results; diff --git a/src/utils/worksite.ts b/src/utils/worksite.ts index 780b4ae6d..2fe625c0d 100644 --- a/src/utils/worksite.ts +++ b/src/utils/worksite.ts @@ -1,4 +1,5 @@ import moment from 'moment'; +import type { AxiosResponse } from 'axios'; import axios from 'axios'; import { DbService, WORKSITE_IMAGES_DATABASE } from '@/services/db.service'; import { generateHash } from './helpers'; @@ -26,6 +27,36 @@ const loadCases = async (query: Record) => { return response.data; }; +const loadCasesPaginated = async (query: Record) => { + const results: CachedCase[] = []; + let nextUrl: string | null = + `${import.meta.env.VITE_APP_API_BASE_URL}/worksites_page`; + let params = { ...query }; + + while (nextUrl) { + const response: AxiosResponse = await axios.get( + nextUrl, + { params }, + ); + const { data } = response; + results.push(...data.results); + nextUrl = data.next; + + // If nextUrl is relative, construct the full URL + if (nextUrl && !nextUrl.startsWith('http')) { + nextUrl = `${import.meta.env.VITE_APP_API_BASE_URL}${nextUrl}`; + } + + // After the first request, params are included in the next URL + params = {}; + } + + return { + count: results.length, + results, + }; +}; + const loadUserLocations = async (query: Record) => { const response = await axios.get( `${import.meta.env.VITE_APP_API_BASE_URL}/user_geo_locations/latest_locations`, @@ -112,6 +143,90 @@ const loadCasesCached = async (query: Record) => { return results; }; +const loadCasesCachedWithPagination = async ( + query: Record, +) => { + const queryKeys = Object.keys(query).sort(); + const sortedQuery: Record = {}; + for (const key of queryKeys) { + sortedQuery[key] = query[key]; + } + + query = { + ...query, + limit: 5000, + }; + + const queryHash = generateHash(JSON.stringify(sortedQuery)); + const cacheKeys = { + CASES: `cachedCases:${queryHash}`, + UPDATED: `casesUpdated:${queryHash}`, + RECONCILED: `casesReconciled:${queryHash}`, + }; + debug('loadCasesCached::QueryHash %o | %s', query, queryHash); + const cachedCases = (await DbService.getItem( + cacheKeys.CASES, + )) as CachedCaseResponse; + const casesUpdatedAt = (await DbService.getItem(cacheKeys.UPDATED)) as string; // ISO date string + const casesReconciledAt = ((await DbService.getItem(cacheKeys.RECONCILED)) || + moment().toISOString()) as string; // ISO date string + + if (cachedCases) { + const [response, reconciliationResponse] = await Promise.all([ + loadCasesPaginated({ + ...query, + updated_at__gt: casesUpdatedAt, + }), + loadCasesPaginated({ + updated_at__gt: casesReconciledAt, + fields: 'id,incident', + }), + ]); + + // Reconcile data + for (const element of reconciliationResponse.results) { + const itemIndex = cachedCases.results.findIndex( + (o) => o.id === element.id, + ); + if ( + itemIndex > -1 && + element.incident !== cachedCases.results[itemIndex].incident + ) { + cachedCases.results.splice(itemIndex, 1); + } + } + + await DbService.setItem(cacheKeys.RECONCILED, moment().toISOString()); + await DbService.setItem(cacheKeys.CASES, cachedCases); + + if (response.results.length === 0) { + return cachedCases; + } + + for (const element of response.results) { + const itemIndex = cachedCases.results.findIndex( + (o) => o.id === element.id, + ); + if (itemIndex > -1) { + cachedCases.results[itemIndex] = element; + } else { + cachedCases.results.push(element); + } + } + + cachedCases.count = cachedCases.results.length; + + await DbService.setItem(cacheKeys.CASES, cachedCases); + await DbService.setItem(cacheKeys.UPDATED, moment().toISOString()); + return cachedCases; + } + + const results = await loadCasesPaginated(query); + await DbService.setItem(cacheKeys.CASES, results); + await DbService.setItem(cacheKeys.UPDATED, moment().toISOString()); + return results; +}; + const loadCaseImagesCached = async (query) => { // Sort the query keys const queryKeys = Object.keys(query).sort(); @@ -324,4 +439,10 @@ const loadCaseImagesCached = async (query) => { return finalData; }; -export { loadCasesCached, loadCases, loadCaseImagesCached, loadUserLocations }; +export { + loadCasesCached, + loadCasesCachedWithPagination, + loadCases, + loadCaseImagesCached, + loadUserLocations, +};