nidus-sync/ts/components/CardPublicReport.vue
Eli Ribble cecb9ef0f0
Some checks failed
/ golint (push) Failing after 10s
Fix reactive nature of generic resource store
These changes are meant to make it possible for new events that come in
to immediately render in the components that depend on them.
2026-05-21 23:12:37 +00:00

185 lines
5 KiB
Vue

<style scoped>
.photo-thumbnail {
width: 100px;
height: 100px;
object-fit: cover;
cursor: pointer;
border-radius: 4px;
transition: transform 0.2s;
}
.photo-thumbnail:hover {
transform: scale(1.05);
}
</style>
<template>
<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>
<h5 class="mb-1">
<span v-if="report.type === 'compliance'">
<i class="bi bi-postcard icon-compliance"></i>
Compliance Report
</span>
<span v-if="report.type === 'nuisance'">
<i class="bi bi-mosquito icon-nuisance"></i>
Nuisance Report
</span>
<span v-if="report.type === 'water'">
<i class="bi bi-droplet-fill icon-standing-water"></i>
Standing Water Report
</span>
</h5>
<small class="text-muted">Report ID: #{{ report.public_id }}</small>
</div>
<span class="badge bg-secondary">
<TimeRelative :time="report.created" />
</span>
</div>
<!-- Common Fields -->
<div class="card mb-3">
<div class="card-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label text-muted small mb-0">
<i class="bi bi-geo-alt"></i> Address
</label>
<div class="fw-medium">
{{ formatAddress(report.address) }}
</div>
</div>
<div class="col-md-6">
<label class="form-label text-muted small mb-0">
<i class="bi bi-person"></i> Reporter Name
</label>
<div class="fw-medium">
{{ report.reporter.name || "not given" }}
</div>
</div>
<div class="col-md-6">
<label
v-if="report.reporter.email != ''"
class="form-label text-muted small mb-0"
>
<i class="bi bi-envelope"></i>
<a :href="'mailto:' + report.reporter.email">{{
report.reporter.email
}}</a>
</label>
<label
v-if="report.reporter.phone.number != ''"
class="form-label text-muted small mb-0"
>
<i class="bi bi-phone"></i>
<a :href="'tel:+' + report.reporter.phone.number">{{
report.reporter.phone.number
}}</a>
</label>
</div>
</div>
<div v-if="report instanceof PublicReportWater" class="row g-3">
<div class="col-12">
<ul>
<li v-if="report.is_reporter_owner">
Reporter is the owner of the property
</li>
<li v-if="report.is_reporter_confidential">
Reporter has asked to be kept confidential
</li>
</ul>
</div>
</div>
</div>
</div>
<div v-if="report instanceof PublicReportCompliance">
<PublicReportCardCompliance :report="report" />
</div>
<div v-if="report instanceof PublicReportNuisance">
<PublicReportCardNuisance :report="report" />
</div>
<div v-if="report instanceof PublicReportWater">
<PublicReportCardWater :report="report" />
</div>
<!-- Images Section -->
<div class="card">
<div
class="card-header d-flex justify-content-between align-items-center"
>
<span><i class="bi bi-images"></i> Attached Photos</span>
<span class="badge bg-primary">
{{ report.images?.length || 0 }}
</span>
</div>
<div class="card-body">
<div
v-if="report.images && report.images.length > 0"
class="d-flex flex-wrap gap-2"
>
<img
v-for="(photo, index) in report.images"
:key="index"
:src="photo.url_content"
class="photo-thumbnail"
@click="openPhotoViewer(index)"
:alt="'Photo ' + (index + 1)"
/>
</div>
<div
v-if="!report.images || report.images.length === 0"
class="text-muted text-center py-3"
>
<i class="bi bi-camera-slash fs-4"></i>
<p class="mb-0 small">No images attached</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted } from "vue";
import { computedAsync } from "@vueuse/core";
import MapMultipoint from "@/components/MapMultipoint.vue";
import TimeRelative from "@/components/TimeRelative.vue";
import PublicReportCardCompliance from "@/components/PublicReportCardCompliance.vue";
import PublicReportCardNuisance from "@/components/PublicReportCardNuisance.vue";
import PublicReportCardWater from "@/components/PublicReportCardWater.vue";
import { formatAddress } from "@/format";
import { log } from "@/log";
import { useStoreResource } from "@/store/resource";
import {
PublicReport,
PublicReportCompliance,
PublicReportNuisance,
PublicReportWater,
} from "@/type/api";
interface Emits {
(e: "viewImage", index: number): void;
}
interface Props {
reportURI: string;
}
const emit = defineEmits<Emits>();
const props = defineProps<Props>();
const storeResource = useStoreResource();
const report = computed(() => {
const result = storeResource.publicreport.byURI.get(props.reportURI);
log.info("geting computed resource", props.reportURI, result);
return result;
});
function openPhotoViewer(index: number) {
emit("viewImage", index);
}
onMounted(async () => {
await storeResource.publicreport.fetchByURI(props.reportURI);
});
</script>