Add generic resource store, start adding context cards
These cards are meant to be generic and can show an unlimited amount of related context about a communication.
This commit is contained in:
parent
c92d23792f
commit
ffb981e40b
7 changed files with 180 additions and 10 deletions
3
ts/components/CardEmail.vue
Normal file
3
ts/components/CardEmail.vue
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<p>fake email card</p>
|
||||||
|
</template>
|
||||||
|
|
@ -12,7 +12,10 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<template>
|
<template>
|
||||||
<div class="details-section p-3 border-top">
|
<div v-if="!report">
|
||||||
|
<p>loading...</p>
|
||||||
|
</div>
|
||||||
|
<div class="details-section p-3 border-top" v-else>
|
||||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-1">
|
<h5 class="mb-1">
|
||||||
|
|
@ -141,13 +144,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
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";
|
||||||
import PublicReportCardCompliance from "@/components/PublicReportCardCompliance.vue";
|
import PublicReportCardCompliance from "@/components/PublicReportCardCompliance.vue";
|
||||||
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 { useStoreResource } from "@/store/resource";
|
||||||
import {
|
import {
|
||||||
PublicReport,
|
PublicReport,
|
||||||
PublicReportCompliance,
|
PublicReportCompliance,
|
||||||
|
|
@ -159,10 +163,15 @@ interface Emits {
|
||||||
(e: "viewImage", index: number): void;
|
(e: "viewImage", index: number): void;
|
||||||
}
|
}
|
||||||
interface Props {
|
interface Props {
|
||||||
report: PublicReport;
|
reportURI: string;
|
||||||
}
|
}
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
const storeResource = useStoreResource();
|
||||||
|
|
||||||
|
const report = computedAsync(() => {
|
||||||
|
return storeResource.publicreport.byURI(props.reportURI);
|
||||||
|
});
|
||||||
function openPhotoViewer(index: number) {
|
function openPhotoViewer(index: number) {
|
||||||
emit("viewImage", index);
|
emit("viewImage", index);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
ts/components/CardResource.vue
Normal file
32
ts/components/CardResource.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<template>
|
||||||
|
<CardEmail v-if="isEmail()" :emailURI="resource.uri" />
|
||||||
|
<CardPublicReport
|
||||||
|
v-if="isPublicReport()"
|
||||||
|
:reportURI="resource.uri"
|
||||||
|
@viewImage="doViewImage"
|
||||||
|
/>
|
||||||
|
<CardText v-if="isText()" :textURI="resource.uri" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ResourceStub } from "@/type/api";
|
||||||
|
import CardEmail from "@/components/CardEmail.vue";
|
||||||
|
import CardPublicReport from "@/components/CardPublicReport.vue";
|
||||||
|
import CardText from "@/components/CardText.vue";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
resource: ResourceStub;
|
||||||
|
}
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
function isEmail(): boolean {
|
||||||
|
return props.resource.type == "email";
|
||||||
|
}
|
||||||
|
function isPublicReport(): boolean {
|
||||||
|
return props.resource.type.startsWith("publicreport.");
|
||||||
|
}
|
||||||
|
function isText(): boolean {
|
||||||
|
return props.resource.type == "text";
|
||||||
|
}
|
||||||
|
function doViewImage() {
|
||||||
|
console.log("doViewImage");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
3
ts/components/CardText.vue
Normal file
3
ts/components/CardText.vue
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<p>fake text card</p>
|
||||||
|
</template>
|
||||||
|
|
@ -74,9 +74,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="h-100 d-flex flex-column" v-else>
|
<div class="h-100 d-flex flex-column" v-else>
|
||||||
<ResourceCard
|
<CardResource
|
||||||
:resource="resource"
|
:resource="resource"
|
||||||
v-for="(resource, index) in selectedCommunication.related"
|
v-for="(resource, index) in sortedByCreated(
|
||||||
|
selectedCommunication.related,
|
||||||
|
)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -86,14 +88,20 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
import ResourceCard from "@/components/ResourceCard.vue";
|
import CardResource from "@/components/CardResource.vue";
|
||||||
import TimeRelative from "@/components/TimeRelative.vue";
|
import TimeRelative from "@/components/TimeRelative.vue";
|
||||||
import Map, { LngLatBounds } from "@/map/Map.vue";
|
import Map, { LngLatBounds } from "@/map/Map.vue";
|
||||||
import Layer from "@/map/Layer.vue";
|
import Layer from "@/map/Layer.vue";
|
||||||
import Source from "@/map/Source.vue";
|
import Source from "@/map/Source.vue";
|
||||||
import { useSessionStore } from "@/store/session";
|
import { useSessionStore } from "@/store/session";
|
||||||
import type { Marker } from "@/types";
|
import type { Marker } from "@/types";
|
||||||
import type { Bounds, Communication, PublicReport, User } from "@/type/api";
|
import type {
|
||||||
|
Bounds,
|
||||||
|
Communication,
|
||||||
|
PublicReport,
|
||||||
|
ResourceStub,
|
||||||
|
User,
|
||||||
|
} from "@/type/api";
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
(e: "viewImage", index: number): void;
|
(e: "viewImage", index: number): void;
|
||||||
|
|
@ -112,4 +120,9 @@ const session = useSessionStore();
|
||||||
function openPhotoViewer(index: number) {
|
function openPhotoViewer(index: number) {
|
||||||
emit("viewImage", index);
|
emit("viewImage", index);
|
||||||
}
|
}
|
||||||
|
function sortedByCreated(resources: ResourceStub[]): ResourceStub[] {
|
||||||
|
return resources.sort((a: ResourceStub, b: ResourceStub): number => {
|
||||||
|
return a.created.getTime() - b.created.getTime();
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
<FlyoverPoolCard :location="signal.location" :markers="[]" />
|
<FlyoverPoolCard :location="signal.location" :markers="[]" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="signal.type == 'publicreport nuisance' && signal.report">
|
<div v-else-if="signal.type == 'publicreport nuisance' && signal.report">
|
||||||
<PublicReportCard :report="signal.report" />
|
<CardPublicReport :reportURI="signal.report.uri" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="signal.type == 'publicreport water' && signal.report">
|
<div v-else-if="signal.type == 'publicreport water' && signal.report">
|
||||||
<PublicReportCard :report="signal.report" />
|
<CardPublicReport :reportURI="signal.report.uri" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<p>No report or pool</p>
|
<p>No report or pool</p>
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import FlyoverPoolCard from "@/components/FlyoverPoolCard.vue";
|
import FlyoverPoolCard from "@/components/FlyoverPoolCard.vue";
|
||||||
import PublicReportCard from "@/components/PublicReportCard.vue";
|
import CardPublicReport from "@/components/CardPublicReport.vue";
|
||||||
import TimeRelative from "@/components/TimeRelative.vue";
|
import TimeRelative from "@/components/TimeRelative.vue";
|
||||||
import { formatAddressShort } from "@/format";
|
import { formatAddressShort } from "@/format";
|
||||||
import { Signal } from "@/type/api";
|
import { Signal } from "@/type/api";
|
||||||
|
|
|
||||||
110
ts/store/resource.ts
Normal file
110
ts/store/resource.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { shallowRef } from "vue";
|
||||||
|
|
||||||
|
import { SSEManager, SSEMessageResource } from "@/SSEManager";
|
||||||
|
import { useSessionStore } from "@/store/session";
|
||||||
|
|
||||||
|
import { apiClient } from "@/client";
|
||||||
|
import {
|
||||||
|
Communication,
|
||||||
|
type CommunicationDTO,
|
||||||
|
PublicReport,
|
||||||
|
type PublicReportDTO,
|
||||||
|
} from "@/type/api";
|
||||||
|
|
||||||
|
interface uriHaver {
|
||||||
|
uri: string;
|
||||||
|
}
|
||||||
|
type jsonConverter<dto, full> = (arg: dto) => full;
|
||||||
|
function createResourceStore<dto, full extends uriHaver>(
|
||||||
|
resource_name: string,
|
||||||
|
api_base: string,
|
||||||
|
from_json: jsonConverter<dto, full>,
|
||||||
|
) {
|
||||||
|
const _resourceByURI = shallowRef<Map<string, full>>(new Map());
|
||||||
|
const _resourceFetchByURI = shallowRef<Map<string, Promise<full> | null>>(
|
||||||
|
new Map(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Subscription
|
||||||
|
SSEManager.subscribe((msg: SSEMessageResource) => {
|
||||||
|
if (msg.resource.startsWith(resource_name) && msg.type == "updated") {
|
||||||
|
fetchByURI(msg.uri);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function byID(id: string): Promise<full> {
|
||||||
|
const uri = uriFromID(id);
|
||||||
|
const cur = _resourceFetchByURI.value.get(uri);
|
||||||
|
if (cur) {
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
return fetchByID(id);
|
||||||
|
}
|
||||||
|
async function byURI(uri: string): Promise<full> {
|
||||||
|
const cur = _resourceFetchByURI.value.get(uri);
|
||||||
|
if (cur) {
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
return fetchByURI(uri);
|
||||||
|
}
|
||||||
|
async function fetchAll(): Promise<full[]> {
|
||||||
|
const sessionStore = useSessionStore();
|
||||||
|
const session = await sessionStore.get();
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("sort", "-created");
|
||||||
|
const url = `${session.urls.api.mailer}?${params}`;
|
||||||
|
const dtos = (await apiClient.JSONGet(url)) as dto[];
|
||||||
|
const resources = dtos.map((m: dto) => from_json(m));
|
||||||
|
resources.forEach((r: full) => {
|
||||||
|
_resourceByURI.value.set(r.uri, r);
|
||||||
|
});
|
||||||
|
return resources;
|
||||||
|
}
|
||||||
|
async function fetchByID(id: string): Promise<full> {
|
||||||
|
const uri = uriFromID(id);
|
||||||
|
return fetchByURI(uri);
|
||||||
|
}
|
||||||
|
async function fetchByURI(uri: string): Promise<full> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(uri);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const body: dto = await response.json();
|
||||||
|
const report = from_json(body);
|
||||||
|
_resourceByURI.value.set(report.uri, report);
|
||||||
|
return report;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error loading users:", err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function loadingURI(uri: string): boolean {
|
||||||
|
return !!_resourceFetchByURI.value.get(uri);
|
||||||
|
}
|
||||||
|
function uriFromID(id: string): string {
|
||||||
|
return `${api_base}/${id}`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
byID,
|
||||||
|
byURI,
|
||||||
|
fetchAll,
|
||||||
|
loadingURI,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export const useStoreResource = defineStore("resource", () => {
|
||||||
|
return {
|
||||||
|
communication: createResourceStore<CommunicationDTO, Communication>(
|
||||||
|
"sync:communication",
|
||||||
|
"/communication",
|
||||||
|
Communication.fromJSON,
|
||||||
|
),
|
||||||
|
publicreport: createResourceStore<PublicReportDTO, PublicReport>(
|
||||||
|
"sync:publicreport",
|
||||||
|
"/publicreport",
|
||||||
|
PublicReport.fromJSON,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue