Move communication workbench to use resource store

Because it's getting better all the time, including by adding the
ability to get new resources when they get created over SSE.
This commit is contained in:
Eli Ribble 2026-05-20 23:49:59 +00:00
parent 72eef554ea
commit b4ae9e5a95
No known key found for this signature in database
6 changed files with 50 additions and 116 deletions

View file

@ -174,7 +174,7 @@ interface Emits {
}
interface Props {
isLoading: boolean;
selectedCommunication: Communication | null;
selectedCommunication: Communication | undefined;
selectedReport: PublicReport | undefined;
}
const emit = defineEmits<Emits>();

View file

@ -110,7 +110,7 @@ interface Props {
loading: boolean;
mapBounds?: LngLatBounds;
mapMarkers: Marker[];
selectedCommunication: Communication | null;
selectedCommunication: Communication | undefined;
selectedReport: PublicReport | undefined;
}

View file

@ -206,7 +206,7 @@ import ListCardCommunication from "@/components/ListCardCommunication.vue";
import { Communication, LogEntry, PublicReport } from "@/type/api";
interface Props {
all: Communication[] | null;
all: Communication[] | undefined;
loading: boolean;
selectedID?: string;
}

View file

@ -1,69 +0,0 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { apiClient } from "@/client";
import { SSEManager, SSEMessageResource } from "@/SSEManager";
import { useSessionStore } from "@/store/session";
import { Communication, CommunicationDTO } from "@/type/api";
export const useCommunicationStore = defineStore("communication", () => {
// State
const all = ref<Communication[] | null>(null);
const loading = ref(false);
const error = ref(null);
// Subscription
SSEManager.subscribe((msg: SSEMessageResource) => {
if (
msg.resource.startsWith("sync:communication") &&
msg.type == "updated"
) {
fetchOne(msg.uri);
}
});
// Actions
async function fetchAll(): Promise<Communication[]> {
const session = useSessionStore();
if (session.urls == null) {
throw new Error("can't fetch without user URL data");
}
loading.value = true;
error.value = null;
try {
const params = new URLSearchParams();
params.append("sort", "-created");
//if (typeFilter.value) params.append("type", typeFilter.value);
const url = `${session.urls.api.communication}?${params}`;
const data = (await apiClient.JSONGet(url)) as CommunicationDTO[];
all.value = data.map((c: CommunicationDTO) => Communication.fromJSON(c));
return all.value;
} catch (err) {
console.error("Error loading communications:", err);
throw err;
} finally {
loading.value = false;
}
}
async function fetchOne(uri: string) {
const data = (await apiClient.JSONGet(uri)) as CommunicationDTO;
if (!all.value) {
return;
}
for (var i = 0; i < all.value.length; i++) {
const c = all.value[i];
if (c.uri == data.uri) {
all.value[i] = Communication.fromJSON(data);
}
}
}
return {
// State
all,
loading,
// Actions
fetchAll,
};
});

View file

@ -31,8 +31,12 @@ function createResourceStore<dto, full extends uriHaver>(
// Subscription
SSEManager.subscribe((msg: SSEMessageResource) => {
if (msg.resource.startsWith(resource_name) && msg.type == "updated") {
fetchByURI(msg.uri);
if (msg.resource.startsWith(resource_name)) {
if (msg.type == "created") {
fetchByURI(msg.uri);
} else if (msg.type == "updated") {
fetchByURI(msg.uri);
}
}
});
@ -92,10 +96,22 @@ function createResourceStore<dto, full extends uriHaver>(
throw err;
}
}
function getAll(): full[] | null {
if (_resourceFetchAll) {
return Array.from(_resourceByURI.value.values());
}
return null;
}
function hasAll(): boolean {
return !!_resourceFetchAll.value;
}
function loadingAll(): boolean {
return !!_resourceFetchAll.value;
}
function loadingURI(uri: string): boolean {
function loadingURI(uri: string | undefined): boolean {
if (uri === undefined) {
return false;
}
return !!_resourceFetchByURI.value.get(uri);
}
function uriFromID(id: string): string {
@ -108,6 +124,8 @@ function createResourceStore<dto, full extends uriHaver>(
fetchAll,
fetchByID,
fetchByURI,
getAll,
hasAll,
loadingAll,
loadingURI,
};

View file

@ -16,14 +16,17 @@
<CommunicationColumnList
:all="visibleCommunications"
@deselect="handleDeselect"
:loading="storeCommunication.loading"
:loading="storeResource.communication.loadingAll()"
:selectedID="selectedId"
@select="handleSelect"
/>
</template>
<template #center>
<CommunicationColumnDetail
:loading="storePublicReport.loading || storeCommunication.loading"
:loading="
storePublicReport.loading ||
storeResource.communication.loadingURI(selectedCommunication?.uri)
"
:mapBounds="mapBounds || undefined"
:mapMarkers="mapMarkers"
:selectedCommunication="selectedCommunication"
@ -33,7 +36,10 @@
</template>
<template #right>
<CommunicationColumnAction
:isLoading="storePublicReport.loading || storeCommunication.loading"
:isLoading="
storePublicReport.loading ||
storeResource.communication.loadingURI(selectedCommunication?.uri)
"
@markInvalid="markInvalid"
@markPendingResponse="markPendingResponse"
@markPossibleIssue="markPossibleIssue"
@ -73,7 +79,7 @@ import ThreeColumn from "@/components/layout/ThreeColumn.vue";
import ToastNotification from "@/components/ToastNotification.vue";
import { useQueryParam } from "@/composable/use-query-param";
import { SSEManager } from "@/SSEManager";
import { useCommunicationStore } from "@/store/communication";
import { useStoreResource } from "@/store/resource";
import { useSessionStore } from "@/store/session";
import type { Marker } from "@/types";
import { Bounds, type Communication, PublicReport } from "@/type/api";
@ -88,8 +94,8 @@ const currentImageIndex = ref<number>(0);
const paramCommunication = useQueryParam("communication");
const selectedId = ref<string | undefined>(undefined);
const showImageModal = ref(false);
const storeCommunication = useCommunicationStore();
const storePublicReport = useStorePublicReport();
const storeResource = useStoreResource();
const toastMessage = ref("");
const toastShow = ref(false);
const toastTitle = ref("");
@ -168,16 +174,13 @@ const mapMarkers = computed<Marker[]>((): Marker[] => {
}
return markers;
});
const selectedCommunication = computed<Communication | null>(
(): Communication | null => {
const selectedCommunication = computedAsync(
async (): Promise<Communication | undefined> => {
if (selectedId.value == undefined) {
return null;
return undefined;
}
if (storeCommunication.all == null) {
return null;
}
const result = storeCommunication.all.find((c) => c.id == selectedId.value);
return result || null;
const all = await storeResource.communication.byAll();
return all.find((c: Communication) => c.id == selectedId.value);
},
);
const selectedReport = computedAsync(
@ -192,14 +195,14 @@ const selectedReport = computedAsync(
return await storePublicReport.byURI(selectedCommunication.value.source);
},
);
const visibleCommunications = computed((): Communication[] => {
if (!storeCommunication.all) {
return [];
}
return storeCommunication.all.filter((c: Communication) => {
return c.status == "new" || c.status == "opened";
});
});
const visibleCommunications = computedAsync(
async (): Promise<Communication[]> => {
const all = await storeResource.communication.byAll();
return all.filter((c: Communication) => {
return c.status == "new" || c.status == "opened";
});
},
);
const handleDeselect = (id: string) => {
selectedId.value = undefined;
};
@ -245,27 +248,9 @@ async function markReport(title: string, status: string) {
`Report Marked ${title}`,
`Report #${selectedCommunication.value.id} has been updated`,
);
removeCurrentFromList();
await storeCommunication.fetchAll();
await storeResource.communication.fetchAll();
}
function removeCurrentFromList() {
if (storeCommunication.all == null) {
return;
}
const index = storeCommunication.all.findIndex(
(c) => c.id === selectedId.value,
);
if (index > -1) {
storeCommunication.all.splice(index, 1);
}
if (storeCommunication.all.length > 0) {
const nextIndex = Math.min(index, storeCommunication.all.length - 1);
selectedId.value = storeCommunication.all[nextIndex].id;
} else {
selectedId.value = undefined;
}
}
async function sendMessage(message: string) {
if (!message.trim()) return;
if (selectedCommunication.value == null) return;
@ -306,7 +291,7 @@ function showNotification(title: string, message: string) {
// Lifecycle hooks
onMounted(async () => {
await storeCommunication.fetchAll();
await storeResource.communication.fetchAll();
});
watch(
paramCommunication.value,