These changes are meant to make it possible for new events that come in to immediately render in the components that depend on them.
This commit is contained in:
parent
74ef9a8b3a
commit
cecb9ef0f0
5 changed files with 92 additions and 59 deletions
|
|
@ -144,6 +144,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted } from "vue";
|
||||||
import { computedAsync } from "@vueuse/core";
|
import { computedAsync } from "@vueuse/core";
|
||||||
import MapMultipoint from "@/components/MapMultipoint.vue";
|
import MapMultipoint from "@/components/MapMultipoint.vue";
|
||||||
import TimeRelative from "@/components/TimeRelative.vue";
|
import TimeRelative from "@/components/TimeRelative.vue";
|
||||||
|
|
@ -151,6 +152,7 @@ import PublicReportCardCompliance from "@/components/PublicReportCardCompliance.
|
||||||
import PublicReportCardNuisance from "@/components/PublicReportCardNuisance.vue";
|
import PublicReportCardNuisance from "@/components/PublicReportCardNuisance.vue";
|
||||||
import PublicReportCardWater from "@/components/PublicReportCardWater.vue";
|
import PublicReportCardWater from "@/components/PublicReportCardWater.vue";
|
||||||
import { formatAddress } from "@/format";
|
import { formatAddress } from "@/format";
|
||||||
|
import { log } from "@/log";
|
||||||
import { useStoreResource } from "@/store/resource";
|
import { useStoreResource } from "@/store/resource";
|
||||||
import {
|
import {
|
||||||
PublicReport,
|
PublicReport,
|
||||||
|
|
@ -169,10 +171,15 @@ const emit = defineEmits<Emits>();
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const storeResource = useStoreResource();
|
const storeResource = useStoreResource();
|
||||||
|
|
||||||
const report = computedAsync(() => {
|
const report = computed(() => {
|
||||||
return storeResource.publicreport.byURI(props.reportURI);
|
const result = storeResource.publicreport.byURI.get(props.reportURI);
|
||||||
|
log.info("geting computed resource", props.reportURI, result);
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
function openPhotoViewer(index: number) {
|
function openPhotoViewer(index: number) {
|
||||||
emit("viewImage", index);
|
emit("viewImage", index);
|
||||||
}
|
}
|
||||||
|
onMounted(async () => {
|
||||||
|
await storeResource.publicreport.fetchByURI(props.reportURI);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import ListCardCommunication from "@/components/ListCardCommunication.vue";
|
import ListCardCommunication from "@/components/ListCardCommunication.vue";
|
||||||
|
import { log } from "@/log";
|
||||||
import { Communication, LogEntry, PublicReport } from "@/type/api";
|
import { Communication, LogEntry, PublicReport } from "@/type/api";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -263,11 +264,15 @@ const activeFilterCount = computed(() => {
|
||||||
|
|
||||||
// Filtered communications
|
// Filtered communications
|
||||||
const filteredCommunications = computed((): Communication[] => {
|
const filteredCommunications = computed((): Communication[] => {
|
||||||
|
log.info(
|
||||||
|
"getting communication column list filteredCommunications",
|
||||||
|
props.all,
|
||||||
|
);
|
||||||
if (props.all == null) {
|
if (props.all == null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return props.all.filter((comm) => {
|
const filtered = props.all.filter((comm) => {
|
||||||
// Status filter
|
// Status filter
|
||||||
const selectedStatuses = Object.entries(statusFilters.value)
|
const selectedStatuses = Object.entries(statusFilters.value)
|
||||||
.filter(([_, isSelected]) => isSelected)
|
.filter(([_, isSelected]) => isSelected)
|
||||||
|
|
@ -299,5 +304,10 @@ const filteredCommunications = computed((): Communication[] => {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
const sorted = filtered.sort((a: Communication, b: Communication): number => {
|
||||||
|
return b.created.getTime() - a.created.getTime();
|
||||||
|
});
|
||||||
|
log.info("filtered and sorted to", sorted);
|
||||||
|
return sorted;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ref, shallowRef } from "vue";
|
import { ref, shallowRef, shallowReactive } from "vue";
|
||||||
|
|
||||||
|
import { log } from "@/log";
|
||||||
import { SSEManager, SSEMessageResource } from "@/SSEManager";
|
import { SSEManager, SSEMessageResource } from "@/SSEManager";
|
||||||
import { useSessionStore } from "@/store/session";
|
|
||||||
|
|
||||||
import { apiClient } from "@/client";
|
import { apiClient } from "@/client";
|
||||||
import {
|
import {
|
||||||
|
|
@ -23,7 +23,7 @@ function createResourceStore<dto, full extends uriHaver>(
|
||||||
api_base: string,
|
api_base: string,
|
||||||
from_json: jsonConverter<dto, full>,
|
from_json: jsonConverter<dto, full>,
|
||||||
) {
|
) {
|
||||||
const _resourceByURI = shallowRef<Map<string, full>>(new Map());
|
const byURI = shallowReactive<Map<string, full>>(new Map());
|
||||||
const _resourceFetchAll = ref<Promise<full[]> | null>(null);
|
const _resourceFetchAll = ref<Promise<full[]> | null>(null);
|
||||||
const _resourceFetchByURI = shallowRef<Map<string, Promise<full> | null>>(
|
const _resourceFetchByURI = shallowRef<Map<string, Promise<full> | null>>(
|
||||||
new Map(),
|
new Map(),
|
||||||
|
|
@ -33,32 +33,43 @@ function createResourceStore<dto, full extends uriHaver>(
|
||||||
SSEManager.subscribe((msg: SSEMessageResource) => {
|
SSEManager.subscribe((msg: SSEMessageResource) => {
|
||||||
if (msg.resource.startsWith(resource_name)) {
|
if (msg.resource.startsWith(resource_name)) {
|
||||||
if (msg.type == "created") {
|
if (msg.type == "created") {
|
||||||
|
console.log("New resource", resource_name, msg.resource, msg.uri);
|
||||||
fetchByURI(msg.uri);
|
fetchByURI(msg.uri);
|
||||||
} else if (msg.type == "updated") {
|
} else if (msg.type == "updated") {
|
||||||
|
console.log("Updated resource", resource_name, msg.resource, msg.uri);
|
||||||
fetchByURI(msg.uri);
|
fetchByURI(msg.uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function byAll(): Promise<full[]> {
|
|
||||||
const cur = _resourceFetchAll.value;
|
|
||||||
if (cur) {
|
|
||||||
return cur;
|
|
||||||
}
|
|
||||||
return fetchAll();
|
|
||||||
}
|
|
||||||
async function byID(id: string): Promise<full> {
|
async function byID(id: string): Promise<full> {
|
||||||
const uri = uriFromID(id);
|
const uri = uriFromID(id);
|
||||||
return byURI(uri);
|
return ensureURI(uri);
|
||||||
}
|
}
|
||||||
async function byURI(uri: string): Promise<full> {
|
async function ensureURI(uri: string): Promise<full> {
|
||||||
let cur = _resourceFetchByURI.value.get(uri);
|
// Check if we already have the data
|
||||||
if (cur) {
|
const existing = byURI.get(uri);
|
||||||
return cur;
|
if (existing) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're already fetching it
|
||||||
|
let fetchPromise = _resourceFetchByURI.value.get(uri);
|
||||||
|
if (fetchPromise) {
|
||||||
|
return fetchPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start fetching
|
||||||
|
fetchPromise = fetchByURI(uri);
|
||||||
|
_resourceFetchByURI.value.set(uri, fetchPromise);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await fetchPromise;
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
// Clean up the promise after it resolves
|
||||||
|
_resourceFetchByURI.value.delete(uri);
|
||||||
}
|
}
|
||||||
cur = fetchByURI(uri);
|
|
||||||
_resourceFetchByURI.value.set(uri, cur);
|
|
||||||
return cur;
|
|
||||||
}
|
}
|
||||||
async function fetchAll(): Promise<full[]> {
|
async function fetchAll(): Promise<full[]> {
|
||||||
/*
|
/*
|
||||||
|
|
@ -71,9 +82,13 @@ function createResourceStore<dto, full extends uriHaver>(
|
||||||
const url = `/api${api_base}`;
|
const url = `/api${api_base}`;
|
||||||
const dtos = (await apiClient.JSONGet(url)) as dto[];
|
const dtos = (await apiClient.JSONGet(url)) as dto[];
|
||||||
const resources = dtos.map((m: dto) => from_json(m));
|
const resources = dtos.map((m: dto) => from_json(m));
|
||||||
|
//let new_all = new Map<string, full>();
|
||||||
resources.forEach((r: full) => {
|
resources.forEach((r: full) => {
|
||||||
_resourceByURI.value.set(r.uri, r);
|
byURI.set(r.uri, r);
|
||||||
|
//new_all.set(r.uri, r);
|
||||||
|
log.info("Set", resource_name, r);
|
||||||
});
|
});
|
||||||
|
//byURI.value = new_all;
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
async function fetchByID(id: string): Promise<full> {
|
async function fetchByID(id: string): Promise<full> {
|
||||||
|
|
@ -89,19 +104,16 @@ function createResourceStore<dto, full extends uriHaver>(
|
||||||
}
|
}
|
||||||
const body: dto = await response.json();
|
const body: dto = await response.json();
|
||||||
const report = from_json(body);
|
const report = from_json(body);
|
||||||
_resourceByURI.value.set(report.uri, report);
|
//let new_all = new Map<string, full>(byURI.value);
|
||||||
|
//new_all.set(report.uri, report);
|
||||||
|
byURI.set(report.uri, report);
|
||||||
|
//byURI.value = new_all;
|
||||||
return report;
|
return report;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error loading users:", err);
|
console.error("Error loading users:", err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function getAll(): full[] | null {
|
|
||||||
if (_resourceFetchAll) {
|
|
||||||
return Array.from(_resourceByURI.value.values());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
function hasAll(): boolean {
|
function hasAll(): boolean {
|
||||||
return !!_resourceFetchAll.value;
|
return !!_resourceFetchAll.value;
|
||||||
}
|
}
|
||||||
|
|
@ -118,13 +130,12 @@ function createResourceStore<dto, full extends uriHaver>(
|
||||||
return `${api_base}/${id}`;
|
return `${api_base}/${id}`;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
byAll,
|
|
||||||
byID,
|
byID,
|
||||||
byURI,
|
byURI,
|
||||||
|
ensureURI,
|
||||||
fetchAll,
|
fetchAll,
|
||||||
fetchByID,
|
fetchByID,
|
||||||
fetchByURI,
|
fetchByURI,
|
||||||
getAll,
|
|
||||||
hasAll,
|
hasAll,
|
||||||
loadingAll,
|
loadingAll,
|
||||||
loadingURI,
|
loadingURI,
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #center>
|
<template #center>
|
||||||
<CommunicationColumnDetail
|
<CommunicationColumnDetail
|
||||||
:loading="
|
:loading="storePublicReport.loading || loadingSelectedCommunication"
|
||||||
storePublicReport.loading ||
|
|
||||||
storeResource.communication.loadingURI(selectedCommunication?.uri)
|
|
||||||
"
|
|
||||||
:mapBounds="mapBounds || undefined"
|
:mapBounds="mapBounds || undefined"
|
||||||
:mapMarkers="mapMarkers"
|
:mapMarkers="mapMarkers"
|
||||||
:selectedCommunication="selectedCommunication"
|
:selectedCommunication="selectedCommunication"
|
||||||
|
|
@ -36,10 +33,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
<CommunicationColumnAction
|
<CommunicationColumnAction
|
||||||
:isLoading="
|
:isLoading="storePublicReport.loading || loadingSelectedCommunication"
|
||||||
storePublicReport.loading ||
|
|
||||||
storeResource.communication.loadingURI(selectedCommunication?.uri)
|
|
||||||
"
|
|
||||||
@markInvalid="markInvalid"
|
@markInvalid="markInvalid"
|
||||||
@markPendingResponse="markPendingResponse"
|
@markPendingResponse="markPendingResponse"
|
||||||
@markPossibleIssue="markPossibleIssue"
|
@markPossibleIssue="markPossibleIssue"
|
||||||
|
|
@ -78,6 +72,7 @@ import ImageViewerModal from "@/components/ImageViewerModal.vue";
|
||||||
import ThreeColumn from "@/components/layout/ThreeColumn.vue";
|
import ThreeColumn from "@/components/layout/ThreeColumn.vue";
|
||||||
import ToastNotification from "@/components/ToastNotification.vue";
|
import ToastNotification from "@/components/ToastNotification.vue";
|
||||||
import { useQueryParam } from "@/composable/use-query-param";
|
import { useQueryParam } from "@/composable/use-query-param";
|
||||||
|
import { log } from "@/log";
|
||||||
import { SSEManager } from "@/SSEManager";
|
import { SSEManager } from "@/SSEManager";
|
||||||
import { useStoreResource } from "@/store/resource";
|
import { useStoreResource } from "@/store/resource";
|
||||||
import { useSessionStore } from "@/store/session";
|
import { useSessionStore } from "@/store/session";
|
||||||
|
|
@ -111,10 +106,13 @@ const currentImages = computed(() => {
|
||||||
}
|
}
|
||||||
return selectedReport.value?.images ?? [];
|
return selectedReport.value?.images ?? [];
|
||||||
});
|
});
|
||||||
|
const loadingSelectedCommunication = computed<boolean>((): boolean => {
|
||||||
|
return !!selectedCommunication.value;
|
||||||
|
});
|
||||||
const mapBounds = computed<LngLatBounds | null>((): LngLatBounds | null => {
|
const mapBounds = computed<LngLatBounds | null>((): LngLatBounds | null => {
|
||||||
let bounds = new Bounds();
|
let bounds = new Bounds();
|
||||||
const loc = selectedReport.value?.location;
|
const loc = selectedReport.value?.location;
|
||||||
console.log("updating for loc", loc);
|
log.info("updating for loc", loc);
|
||||||
if (loc && loc.latitude != 0 && loc.longitude != 0) {
|
if (loc && loc.latitude != 0 && loc.longitude != 0) {
|
||||||
bounds.addLocation(loc);
|
bounds.addLocation(loc);
|
||||||
}
|
}
|
||||||
|
|
@ -174,15 +172,16 @@ const mapMarkers = computed<Marker[]>((): Marker[] => {
|
||||||
}
|
}
|
||||||
return markers;
|
return markers;
|
||||||
});
|
});
|
||||||
const selectedCommunication = computedAsync(
|
const selectedCommunication = computed((): Communication | undefined => {
|
||||||
async (): Promise<Communication | undefined> => {
|
log.info("get selectedCommunication", selectedId.value);
|
||||||
if (selectedId.value == undefined) {
|
if (!selectedId.value) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const all = await storeResource.communication.byAll();
|
const all = Array.from(storeResource.communication.byURI.values());
|
||||||
return all.find((c: Communication) => c.id == selectedId.value);
|
const result = all.find((c: Communication) => c.id == selectedId.value);
|
||||||
},
|
log.info("selectedCommunication", selectedId.value, result);
|
||||||
);
|
return result;
|
||||||
|
});
|
||||||
const selectedReport = computedAsync(
|
const selectedReport = computedAsync(
|
||||||
async (): Promise<PublicReport | undefined> => {
|
async (): Promise<PublicReport | undefined> => {
|
||||||
if (
|
if (
|
||||||
|
|
@ -195,14 +194,20 @@ const selectedReport = computedAsync(
|
||||||
return await storePublicReport.byURI(selectedCommunication.value.source);
|
return await storePublicReport.byURI(selectedCommunication.value.source);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const visibleCommunications = computedAsync(
|
const visibleCommunications = computed((): Communication[] | undefined => {
|
||||||
async (): Promise<Communication[]> => {
|
log.info("get visibleCommunications", storeResource.communication.byURI);
|
||||||
const all = await storeResource.communication.byAll();
|
if (!storeResource.communication.byURI) {
|
||||||
return all.filter((c: Communication) => {
|
return undefined;
|
||||||
return c.status == "new" || c.status == "opened";
|
}
|
||||||
});
|
const all: Communication[] = Array.from(
|
||||||
},
|
storeResource.communication.byURI.values(),
|
||||||
);
|
);
|
||||||
|
const result = all.filter((c: Communication) => {
|
||||||
|
return c.status == "new" || c.status == "opened";
|
||||||
|
});
|
||||||
|
log.info("visibleCommunications:", result);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
const handleDeselect = (id: string) => {
|
const handleDeselect = (id: string) => {
|
||||||
selectedId.value = undefined;
|
selectedId.value = undefined;
|
||||||
};
|
};
|
||||||
|
|
@ -256,7 +261,7 @@ async function sendMessage(message: string) {
|
||||||
if (selectedCommunication.value == null) return;
|
if (selectedCommunication.value == null) return;
|
||||||
if (selectedReport.value == null) return;
|
if (selectedReport.value == null) return;
|
||||||
if (session.urls == null) return;
|
if (session.urls == null) return;
|
||||||
console.log("Sending message reporter:", message);
|
log.info("Sending message reporter:", message);
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
message: message,
|
message: message,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { Contact } from "@/type/api";
|
||||||
|
|
||||||
const storeResource = useStoreResource();
|
const storeResource = useStoreResource();
|
||||||
const contacts = computedAsync(() => {
|
const contacts = computedAsync(() => {
|
||||||
return storeResource.contact.byAll();
|
return Array.from(storeResource.contact.byURI.values());
|
||||||
});
|
});
|
||||||
const selectedContact = ref<Contact | undefined>(undefined);
|
const selectedContact = ref<Contact | undefined>(undefined);
|
||||||
const handleSelectionChange = (selection: Contact | undefined) => {
|
const handleSelectionChange = (selection: Contact | undefined) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue