2026-04-06 22:38:17 +00:00
|
|
|
<style scoped>
|
|
|
|
|
body {
|
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body > .container-fluid {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.progress-bar {
|
|
|
|
|
background-color: #0d6efd;
|
|
|
|
|
transition: width 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<template>
|
|
|
|
|
<template v-if="district">
|
2026-04-07 00:04:40 +00:00
|
|
|
<router-view v-slot="{ Component }">
|
2026-04-10 15:38:31 +00:00
|
|
|
<LoadingOverlay
|
|
|
|
|
:is-loading="isLoading"
|
|
|
|
|
loading-text="Loading previous data"
|
|
|
|
|
>
|
|
|
|
|
<component
|
|
|
|
|
:is="Component"
|
|
|
|
|
:district="district"
|
|
|
|
|
@doEvidence="doEvidence"
|
|
|
|
|
@doContact="doContact"
|
|
|
|
|
@doLocator="doLocator"
|
|
|
|
|
@doPermission="doPermission"
|
|
|
|
|
v-model="compliance"
|
|
|
|
|
/>
|
|
|
|
|
</LoadingOverlay>
|
2026-04-07 00:04:40 +00:00
|
|
|
</router-view>
|
2026-04-06 22:38:17 +00:00
|
|
|
</template>
|
|
|
|
|
<template v-else>
|
|
|
|
|
<p>loading {{ slug }}...</p>
|
|
|
|
|
</template>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, onMounted, ref } from "vue";
|
|
|
|
|
import { computedAsync } from "@vueuse/core";
|
|
|
|
|
|
2026-04-09 17:35:00 +00:00
|
|
|
import type { Image } from "@/components/ImageUpload.vue";
|
2026-04-08 17:49:32 +00:00
|
|
|
import { useStoreDistrict } from "@/rmo/store/district";
|
2026-04-09 23:38:20 +00:00
|
|
|
import { useStoreLocal } from "@/store/local";
|
|
|
|
|
import { useStoreLocation } from "@/store/location";
|
2026-04-06 22:38:17 +00:00
|
|
|
import Intro from "@/rmo/content/compliance/Intro.vue";
|
2026-04-10 15:38:31 +00:00
|
|
|
import LoadingOverlay from "@/components/LoadingOverlay.vue";
|
|
|
|
|
import type { District, PublicReport } from "@/type/api";
|
|
|
|
|
import { Address, Location, PermissionAccess } from "@/type/api";
|
2026-04-09 17:21:35 +00:00
|
|
|
import { Locator } from "@/type/map";
|
2026-04-09 20:55:30 +00:00
|
|
|
import { type Contact } from "@/rmo/content/compliance/Contact.vue";
|
|
|
|
|
import { type Permission } from "@/rmo/content/compliance/Permission.vue";
|
2026-04-09 17:21:35 +00:00
|
|
|
|
2026-04-09 20:55:30 +00:00
|
|
|
export interface Compliance {
|
|
|
|
|
comments: string;
|
2026-04-09 22:42:47 +00:00
|
|
|
contact: Contact;
|
2026-04-10 15:38:31 +00:00
|
|
|
id: string;
|
|
|
|
|
images: Image[];
|
2026-04-10 14:20:04 +00:00
|
|
|
location: Location;
|
2026-04-09 22:22:27 +00:00
|
|
|
locator: Locator;
|
2026-04-09 22:42:47 +00:00
|
|
|
permission: Permission;
|
2026-04-10 15:38:31 +00:00
|
|
|
uri: string;
|
2026-04-09 20:55:30 +00:00
|
|
|
}
|
2026-04-06 22:38:17 +00:00
|
|
|
interface Props {
|
|
|
|
|
slug: string;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 17:49:32 +00:00
|
|
|
const districtStore = useStoreDistrict();
|
2026-04-06 22:38:17 +00:00
|
|
|
|
2026-04-09 20:55:30 +00:00
|
|
|
const compliance = ref<Compliance>({
|
|
|
|
|
comments: "",
|
2026-04-09 22:42:47 +00:00
|
|
|
contact: {
|
|
|
|
|
name: "",
|
|
|
|
|
phone: "",
|
|
|
|
|
can_text: true,
|
|
|
|
|
email: "",
|
|
|
|
|
},
|
2026-04-10 15:38:31 +00:00
|
|
|
id: "",
|
2026-04-09 20:55:30 +00:00
|
|
|
images: [],
|
2026-04-10 14:20:04 +00:00
|
|
|
location: {
|
|
|
|
|
latitude: 0,
|
|
|
|
|
longitude: 0,
|
|
|
|
|
},
|
2026-04-09 22:22:27 +00:00
|
|
|
locator: {
|
2026-04-10 15:38:31 +00:00
|
|
|
address: new Address(),
|
|
|
|
|
location: new Location(),
|
2026-04-09 22:22:27 +00:00
|
|
|
},
|
2026-04-09 22:42:47 +00:00
|
|
|
permission: {
|
|
|
|
|
access: PermissionAccess.UNSELECTED,
|
|
|
|
|
access_instructions: "",
|
|
|
|
|
availability_notes: "",
|
|
|
|
|
gate_code: "",
|
|
|
|
|
has_dog: false,
|
|
|
|
|
wants_scheduled: false,
|
|
|
|
|
},
|
2026-04-10 15:38:31 +00:00
|
|
|
uri: "",
|
2026-04-09 20:55:30 +00:00
|
|
|
});
|
2026-04-10 15:38:31 +00:00
|
|
|
const isLoading = ref<boolean>(true);
|
2026-04-06 22:38:17 +00:00
|
|
|
const props = defineProps<Props>();
|
2026-04-09 23:38:20 +00:00
|
|
|
const report = ref<PublicReport | null>();
|
2026-04-06 22:38:17 +00:00
|
|
|
const district = computedAsync(async (): Promise<District | undefined> => {
|
2026-04-08 17:49:32 +00:00
|
|
|
const districts = await districtStore.list();
|
2026-04-06 22:38:17 +00:00
|
|
|
return districts.find((district: District) => district.slug == props.slug);
|
|
|
|
|
});
|
2026-04-09 23:38:20 +00:00
|
|
|
const storeLocal = useStoreLocal();
|
|
|
|
|
const storeLocation = useStoreLocation();
|
2026-04-10 15:38:31 +00:00
|
|
|
async function createReport(client_id: string, loc?: GeolocationPosition) {
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("client_id", client_id);
|
|
|
|
|
if (loc) {
|
|
|
|
|
formData.append("location.accuracy", loc.coords.accuracy.toString());
|
|
|
|
|
formData.append("location.latitude", loc.coords.latitude.toString());
|
|
|
|
|
formData.append("location.longitude", loc.coords.longitude.toString());
|
|
|
|
|
} else {
|
|
|
|
|
formData.append("location.accuracy", "0");
|
|
|
|
|
formData.append("location.latitude", "0");
|
|
|
|
|
formData.append("longitude", "0");
|
|
|
|
|
}
|
|
|
|
|
const resp = await fetch("/api/rmo/compliance", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: formData,
|
|
|
|
|
// Don't set Content-Type, the borwser should do it
|
|
|
|
|
});
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
const content = await resp.text();
|
|
|
|
|
console.error("Failed to create compliance report", resp.status, content);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-09 22:42:47 +00:00
|
|
|
function doEvidence() {
|
|
|
|
|
console.log("evidence", compliance.value);
|
2026-04-09 17:35:00 +00:00
|
|
|
}
|
2026-04-09 22:42:47 +00:00
|
|
|
function doContact() {
|
|
|
|
|
console.log("contact", compliance.value.contact);
|
2026-04-09 17:35:00 +00:00
|
|
|
}
|
2026-04-09 22:22:27 +00:00
|
|
|
function doLocator() {
|
|
|
|
|
console.log("locator done", compliance.value.locator);
|
2026-04-10 15:38:31 +00:00
|
|
|
updateReport({
|
|
|
|
|
locator: compliance.value.locator,
|
|
|
|
|
});
|
2026-04-09 20:55:30 +00:00
|
|
|
}
|
2026-04-09 22:42:47 +00:00
|
|
|
function doPermission() {
|
|
|
|
|
console.log("permission", compliance.value.permission);
|
2026-04-09 17:21:35 +00:00
|
|
|
}
|
2026-04-10 15:38:31 +00:00
|
|
|
interface ComplianceUpdate {
|
|
|
|
|
locator?: Locator;
|
|
|
|
|
}
|
|
|
|
|
async function updateReport(updates: ComplianceUpdate) {
|
|
|
|
|
const resp = await fetch(compliance.value.uri, {
|
|
|
|
|
method: "PUT",
|
|
|
|
|
body: JSON.stringify(updates),
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
},
|
2026-04-09 23:38:20 +00:00
|
|
|
});
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
const content = await resp.text();
|
2026-04-10 15:38:31 +00:00
|
|
|
console.error("Failed to update compliance", resp.status, content);
|
|
|
|
|
return;
|
2026-04-09 23:38:20 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
onMounted(() => {
|
2026-04-10 15:38:31 +00:00
|
|
|
const client_id = storeLocal.getClientID();
|
2026-04-09 23:38:20 +00:00
|
|
|
storeLocation
|
|
|
|
|
.get()
|
|
|
|
|
.then((loc: GeolocationPosition) => {
|
2026-04-10 14:20:04 +00:00
|
|
|
compliance.value.location = loc.coords;
|
2026-04-10 15:38:31 +00:00
|
|
|
createReport(client_id, loc);
|
2026-04-09 23:38:20 +00:00
|
|
|
})
|
|
|
|
|
.catch((e) => {
|
|
|
|
|
console.log("failed to get location", e);
|
2026-04-10 15:38:31 +00:00
|
|
|
createReport(client_id);
|
2026-04-09 23:38:20 +00:00
|
|
|
});
|
|
|
|
|
});
|
2026-04-06 22:38:17 +00:00
|
|
|
</script>
|