nidus-sync/ts/rmo/view/Compliance.vue

269 lines
6.5 KiB
Vue
Raw Normal View History

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;
}
.reference-number {
text-align: center;
color: #6c757d;
font-size: 0.9rem;
margin-top: 24px;
}
2026-04-06 22:38:17 +00:00
</style>
<template>
<router-view v-slot="{ Component }">
<LoadingOverlay
:is-loading="isLoading"
loading-text="Loading previous data"
>
<template v-if="!isLoading">
<component
:is="Component"
:district="district"
@doAddress="doAddress"
@doContact="doContact"
@doEvidence="doEvidence"
@doPermission="doPermission"
@doSubmit="doSubmit"
v-model="report"
/>
</template>
</LoadingOverlay>
</router-view>
<!-- Reference Number -->
<div class="reference-number" v-if="report && report.public_id">
<small>
Reference number: <strong>{{ report.public_id }}</strong>
</small>
</div>
2026-04-06 22:38:17 +00:00
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { computedAsync } from "@vueuse/core";
import type { Image } from "@/components/ImageUpload.vue";
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";
import LoadingOverlay from "@/components/LoadingOverlay.vue";
import {
type ComplianceUpdate,
type District,
PublicReport,
PublicReportCompliance,
PublicReportComplianceOptions,
} from "@/type/api";
import { Contact, Address, Location, PermissionType } from "@/type/api";
2026-04-06 22:38:17 +00:00
interface Props {
slug: string;
}
const districtStore = useStoreDistrict();
2026-04-06 22:38:17 +00:00
const isLoading = ref<boolean>(true);
2026-04-10 22:34:14 +00:00
const isUploading = ref<boolean>(false);
2026-04-06 22:38:17 +00:00
const props = defineProps<Props>();
const report = ref<PublicReportCompliance>(new PublicReportCompliance());
const district = ref<District | undefined>(undefined);
2026-04-09 23:38:20 +00:00
const storeLocal = useStoreLocal();
const storeLocation = useStoreLocation();
async function createReport(client_id: string, district_uri: string) {
let content = {
client_id: client_id,
district: district_uri,
location: {
accuracy: 0,
latitude: 0,
longitude: 0,
},
};
const resp = await fetch("/api/rmo/compliance", {
body: JSON.stringify(content),
headers: {
"Content-Type": "application/json",
},
method: "POST",
});
if (!resp.ok) {
const content = await resp.text();
console.error("Failed to create compliance report", resp.status, content);
return;
}
const body = await resp.json();
storeLocal.setExistingComplianceReportURI(body.uri);
report.value!.public_id = body.public_id;
report.value!.uri = body.uri;
}
function doAddress() {
if (!report.value) {
console.log("can't do address, null report");
return;
}
console.log("address done", report.value.address);
updateReport({
address: report.value.address,
});
}
function doEvidence(images: Image[]) {
if (!report.value) {
console.log("can't do evidence, null report");
return;
}
uploadImages(images);
if (report.value.comments) {
updateReport({
comments: report.value.comments,
});
}
}
function doContact() {
if (!report.value) {
console.log("can't do contact, null report");
return;
}
console.log(
"contact",
JSON.stringify(report.value.reporter),
report.value.reporter,
);
updateReport({
reporter: report.value.reporter,
});
}
function doPermission() {
if (!report.value) {
console.log("can't do permission, null report");
return;
}
console.log("report.value.has_dog", report.value.has_dog);
updateReport({
access_instructions: report.value.access_instructions,
availability_notes: report.value.availability_notes,
gate_code: report.value.gate_code,
has_dog: report.value.has_dog,
permission_type: report.value.permission_type,
wants_scheduled: report.value.wants_scheduled,
});
}
function doSubmit() {
console.log("submit", report.value);
storeLocal.delExistingComplianceReportURI();
}
async function fetchExistingReport(report_uri: string) {
const resp = await fetch(report_uri);
if (!resp.ok) {
const content = await resp.text();
console.error(
"Failed to fetch existing report",
report_uri,
resp.status,
content,
);
return;
}
const body = (await resp.json()) as PublicReportComplianceOptions;
Object.assign(report.value, new PublicReportCompliance(body));
2026-04-13 15:16:22 +00:00
console.log("fetched existing report", report.value);
}
async function updateReport(updates: ComplianceUpdate) {
if (!report.value.uri) {
console.log("Refusing to update report without URI");
return;
}
const resp = await fetch(report.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();
console.error("Failed to update compliance", resp.status, content);
return;
2026-04-09 23:38:20 +00:00
}
}
async function updateLocation() {
const loc = await storeLocation.get();
report.value.location = loc.coords;
updateReport({
location: report.value.location,
});
}
2026-04-10 22:34:14 +00:00
async function uploadImages(images: Image[]) {
2026-04-13 20:42:03 +00:00
if (images.length == 0) return;
2026-04-10 22:34:14 +00:00
isUploading.value = true;
const formData = new FormData();
images.map(async (image, index) => {
formData.append(`image[${index}]`, image.file, image.name);
});
const url = `${report.value.uri}/image`;
2026-04-10 22:34:14 +00:00
const response = await fetch(url, {
body: formData,
method: "POST",
});
if (!response.ok) {
const content = await response.text();
console.error(
"Failed to POST images",
url,
response.status,
response.statusText,
content,
);
isUploading.value = false;
return;
}
isUploading.value = false;
// after everything is done update the report so that we see the correct number of images
// on the report summary
await fetchExistingReport(report.value.uri);
2026-04-10 22:34:14 +00:00
}
2026-04-09 23:38:20 +00:00
onMounted(() => {
const client_id = storeLocal.getClientID();
const report_uri = storeLocal.getExistingComplianceReportURI();
if (report_uri) {
fetchExistingReport(report_uri).then(() => {
updateLocation();
});
}
const districts = districtStore
.list()
.then((districts: District[]) => {
const d = districts.find(
(district: District) => district.slug == props.slug,
);
if (!d) {
console.error(
"Failed to find district with slug",
districts,
props.slug,
);
return;
}
district.value = d;
if (!report_uri) {
createReport(client_id, d.uri);
}
isLoading.value = false;
updateLocation();
2026-04-09 23:38:20 +00:00
})
.catch((e) => {
console.error("Failed to list districts", e);
isLoading.value = false;
2026-04-09 23:38:20 +00:00
});
});
2026-04-06 22:38:17 +00:00
</script>