WIP: creating contact resource
Some checks failed
/ golint (push) Has been cancelled

This commit is contained in:
Eli Ribble 2026-05-15 20:10:14 +00:00
parent 8b203908a0
commit 725945d95c
No known key found for this signature in database
22 changed files with 381 additions and 135 deletions

View file

@ -61,21 +61,21 @@
</div>
<div class="col-md-6">
<label
v-if="report.reporter.has_email"
v-if="report.reporter.emails.length"
class="form-label text-muted small mb-0"
>
<i class="bi bi-envelope"></i>
<a :href="'mailto:' + report.reporter.email">{{
report.reporter.email
<a :href="'mailto:' + report.reporter.emails[0]">{{
report.reporter.emails[0]
}}</a>
</label>
<label
v-if="report.reporter.has_phone"
v-if="report.reporter.phones.length"
class="form-label text-muted small mb-0"
>
<i class="bi bi-phone"></i>
<a :href="'tel:+' + report.reporter.phone">{{
report.reporter.phone
<a :href="'tel:+' + report.reporter.phones[0]">{{
report.reporter.phones[0]
}}</a>
</label>
</div>

View file

@ -78,8 +78,8 @@
<div
v-if="
!(
selectedReport?.reporter.has_email ||
selectedReport?.reporter.has_phone
selectedReport?.reporter.emails.length ||
selectedReport?.reporter.phones.length
)
"
class="mb-3"
@ -91,8 +91,8 @@
</div>
<div
v-if="
selectedReport?.reporter.has_email ||
selectedReport?.reporter.has_phone
selectedReport?.reporter.emails.length ||
selectedReport?.reporter.phones.length
"
class="mb-3"
>

View file

@ -87,13 +87,13 @@
</div>
<div class="col-md-6">
<label
v-if="report.owner.has_email"
v-if="report.owner.emails.length"
class="form-label text-muted small mb-0"
>
<i class="bi bi-envelope"></i>
</label>
<label
v-if="report.owner.has_phone"
v-if="report.owner.phones.length"
class="form-label text-muted small mb-0"
>
<i class="bi bi-phone"></i>

View file

@ -248,10 +248,8 @@ const hasCompleteResponse = computed(() => {
r.images.length > 0 ||
r.permission_type == PermissionType.GRANTED ||
r.reporter.name ||
r.reporter.phone ||
r.reporter.has_phone ||
r.reporter.email ||
r.reporter.has_email
r.reporter.phones.length ||
r.reporter.emails.length
) {
return true;
}

View file

@ -61,7 +61,7 @@
id="contact-phone"
name="phone"
placeholder="(555) 123-4567"
v-model="modelValue.reporter.phone"
v-model="modelValue.reporter.phone.number"
/>
</div>
@ -92,7 +92,7 @@
<div
class="alert alert-primary"
role="alert"
v-if="modelValue.reporter.has_email"
v-if="modelValue.reporter.email != ''"
>
You've already added an email address to this report. If you alter the
email below, it will replace the current email address.

View file

@ -199,7 +199,7 @@
<div
class="summary-value"
v-if="
modelValue.reporter?.phone || modelValue.reporter?.has_phone
modelValue.reporter?.phone || modelValue.reporter?.phone != ''
"
>
{{ modelValue.reporter.phone }}
@ -216,7 +216,7 @@
<div
class="summary-value"
v-if="
modelValue.reporter?.email || modelValue.reporter?.has_email
modelValue.reporter?.email || modelValue.reporter?.email != ''
"
>
{{ modelValue.reporter?.email }}

View file

@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import { shallowRef } from "vue";
import { ref, shallowRef } from "vue";
import { SSEManager, SSEMessageResource } from "@/SSEManager";
import { useSessionStore } from "@/store/session";
@ -8,6 +8,8 @@ import { apiClient } from "@/client";
import {
Communication,
type CommunicationDTO,
Contact,
type ContactDTO,
PublicReport,
type PublicReportDTO,
} from "@/type/api";
@ -22,6 +24,7 @@ function createResourceStore<dto, full extends uriHaver>(
from_json: jsonConverter<dto, full>,
) {
const _resourceByURI = shallowRef<Map<string, full>>(new Map());
const _resourceFetchAll = ref<Promise<full[]> | null>(null);
const _resourceFetchByURI = shallowRef<Map<string, Promise<full> | null>>(
new Map(),
);
@ -33,20 +36,25 @@ function createResourceStore<dto, full extends uriHaver>(
}
});
async function byAll(): Promise<full[]> {
const cur = _resourceFetchAll.value;
if (cur) {
return cur;
}
return fetchAll();
}
async function byID(id: string): Promise<full> {
const uri = uriFromID(id);
const cur = _resourceFetchByURI.value.get(uri);
if (cur) {
return cur;
}
return fetchByID(id);
return byURI(uri);
}
async function byURI(uri: string): Promise<full> {
const cur = _resourceFetchByURI.value.get(uri);
let cur = _resourceFetchByURI.value.get(uri);
if (cur) {
return cur;
}
return fetchByURI(uri);
cur = fetchByURI(uri);
_resourceFetchByURI.value.set(uri, cur);
return cur;
}
async function fetchAll(): Promise<full[]> {
const sessionStore = useSessionStore();
@ -88,9 +96,12 @@ function createResourceStore<dto, full extends uriHaver>(
return `${api_base}/${id}`;
}
return {
byAll,
byID,
byURI,
fetchAll,
fetchByID,
fetchByURI,
loadingURI,
};
}
@ -101,6 +112,11 @@ export const useStoreResource = defineStore("resource", () => {
"/communication",
Communication.fromJSON,
),
contact: createResourceStore<ContactDTO, Contact>(
"sync:contact",
"/contact",
Contact.fromJSON,
),
publicreport: createResourceStore<PublicReportDTO, PublicReport>(
"sync:publicreport",
"/publicreport",

View file

@ -77,28 +77,47 @@ export class Bounds {
}
}
export interface ContactOptions {
can_sms: boolean;
email?: string;
has_email: boolean;
has_phone: boolean;
emails?: string[];
name?: string;
phone?: string;
phones?: Phone[];
uri?: string;
}
export interface Phone {
can_sms: boolean;
e164: string;
}
export interface PhoneReporter {
can_sms: boolean;
number: string;
}
export class Contact {
can_sms: boolean;
email: string;
has_email: boolean;
has_phone: boolean;
emails: string[];
name: string;
phone: string;
phones: Phone[];
uri: string;
constructor(options?: ContactOptions) {
this.can_sms = options?.can_sms ?? false;
this.email = options?.email ?? "";
this.has_email = options?.has_email ?? false;
this.has_phone = options?.has_phone ?? false;
this.emails = options?.emails ?? [];
this.name = options?.name ?? "";
this.phone = options?.phone ?? "";
this.phones = options?.phones ?? [];
this.uri = options?.uri ?? "";
}
static fromJSON(json: ContactDTO): Contact {
return new Contact(json);
}
}
export interface ContactDTO {
name: string;
emails: string[];
phones: Phone[];
uri: string;
}
export class ContactReporter {
constructor(
public name: string,
public email: string,
public phone: Phone,
public uri: string,
) {}
}
export interface District {
name: string;
@ -195,7 +214,7 @@ export interface ComplianceUpdate {
//images?: Image[];
location?: Location;
permission_type?: string;
reporter?: Contact;
reporter?: ContactReporter;
submitted?: string;
//uri: string;
wants_scheduled?: boolean;
@ -212,7 +231,7 @@ export interface PublicReportDTO {
location: Location;
log: LogEntryDTO[];
public_id: string;
reporter: Contact;
reporter: ContactReporter;
status: string;
type: string;
uri: string;
@ -224,7 +243,7 @@ export interface PublicReportUpdate {
images?: Image[];
location?: Location;
public_id?: string;
reporter?: Contact;
reporter?: ContactReporter;
status?: string;
type?: string;
uri?: string;
@ -242,7 +261,7 @@ export interface PublicReportOptions {
location: Location;
log: LogEntry[];
public_id: string;
reporter: Contact;
reporter: ContactReporter;
status: string;
type: string;
uri: string;
@ -254,7 +273,7 @@ export class PublicReport {
images: Image[];
log: LogEntry[];
public_id: string;
reporter: Contact;
reporter: ContactReporter;
status: string;
type: string;
uri: string;
@ -266,7 +285,7 @@ export class PublicReport {
this.images = options?.images ?? [];
this.log = options?.log ?? [];
this.public_id = options?.public_id ?? "";
this.reporter = options?.reporter ?? new Contact();
this.reporter = options?.reporter ?? new ContactReporter();
this.status = options?.status ?? "";
this.type = options?.type ?? "";
this.uri = options?.uri ?? "";

View file

@ -12,8 +12,16 @@
</ThreeColumn>
</template>
<script setup lang="ts">
import { computedAsync } from "@vueuse/core";
import ThreeColumn from "@/components/layout/ThreeColumn.vue";
import ReviewContactColumnAction from "@/components/ReviewContactColumnAction.vue";
import ReviewContactColumnDetail from "@/components/ReviewContactColumnDetail.vue";
import ReviewContactColumnList from "@/components/ReviewContactColumnList.vue";
import { useStoreResource } from "@/store/resource";
const storeResource = useStoreResource();
const contacts = computedAsync(() => {
return storeResource.contact.byAll();
});
</script>