Add loading indicator when checking for previous report data
This commit is contained in:
parent
b23fc6edc5
commit
14c0d453e9
4 changed files with 158 additions and 55 deletions
75
ts/components/LoadingOverlay.vue
Normal file
75
ts/components/LoadingOverlay.vue
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<div class="loading-wrapper position-relative">
|
||||||
|
<!-- Child content slot -->
|
||||||
|
<slot></slot>
|
||||||
|
|
||||||
|
<!-- Loading overlay -->
|
||||||
|
<div
|
||||||
|
v-if="isLoading"
|
||||||
|
class="loading-overlay position-absolute top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center"
|
||||||
|
:class="overlayClass"
|
||||||
|
>
|
||||||
|
<div class="text-center">
|
||||||
|
<!-- Bootstrap spinner -->
|
||||||
|
<div
|
||||||
|
class="spinner-border text-primary"
|
||||||
|
:class="spinnerSize"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<!-- Optional loading text -->
|
||||||
|
<div v-if="loadingText" class="mt-2">
|
||||||
|
<small class="text-muted">{{ loadingText }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "LoadingOverlay",
|
||||||
|
props: {
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
loadingText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
spinnerSize: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
validator: (value) => ["", "spinner-border-sm"].includes(value),
|
||||||
|
},
|
||||||
|
overlayOpacity: {
|
||||||
|
type: String,
|
||||||
|
default: "bg-light bg-opacity-75",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
overlayClass() {
|
||||||
|
return this.overlayOpacity;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.loading-wrapper {
|
||||||
|
min-height: 50px; /* Ensure minimum height for overlay positioning */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-overlay {
|
||||||
|
z-index: 1000;
|
||||||
|
backdrop-filter: blur(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure child content is not interactive when loading */
|
||||||
|
.loading-wrapper:has(.loading-overlay) > *:not(.loading-overlay) {
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -18,15 +18,20 @@ body > .container-fluid {
|
||||||
<template>
|
<template>
|
||||||
<template v-if="district">
|
<template v-if="district">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<component
|
<LoadingOverlay
|
||||||
:is="Component"
|
:is-loading="isLoading"
|
||||||
:district="district"
|
loading-text="Loading previous data"
|
||||||
@doEvidence="doEvidence"
|
>
|
||||||
@doContact="doContact"
|
<component
|
||||||
@doLocator="doLocator"
|
:is="Component"
|
||||||
@doPermission="doPermission"
|
:district="district"
|
||||||
v-model="compliance"
|
@doEvidence="doEvidence"
|
||||||
/>
|
@doContact="doContact"
|
||||||
|
@doLocator="doLocator"
|
||||||
|
@doPermission="doPermission"
|
||||||
|
v-model="compliance"
|
||||||
|
/>
|
||||||
|
</LoadingOverlay>
|
||||||
</router-view>
|
</router-view>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|
@ -43,8 +48,9 @@ import { useStoreDistrict } from "@/rmo/store/district";
|
||||||
import { useStoreLocal } from "@/store/local";
|
import { useStoreLocal } from "@/store/local";
|
||||||
import { useStoreLocation } from "@/store/location";
|
import { useStoreLocation } from "@/store/location";
|
||||||
import Intro from "@/rmo/content/compliance/Intro.vue";
|
import Intro from "@/rmo/content/compliance/Intro.vue";
|
||||||
import type { District, Location, PublicReport } from "@/type/api";
|
import LoadingOverlay from "@/components/LoadingOverlay.vue";
|
||||||
import { PermissionAccess } from "@/type/api";
|
import type { District, PublicReport } from "@/type/api";
|
||||||
|
import { Address, Location, PermissionAccess } from "@/type/api";
|
||||||
import { Locator } from "@/type/map";
|
import { Locator } from "@/type/map";
|
||||||
import { type Contact } from "@/rmo/content/compliance/Contact.vue";
|
import { type Contact } from "@/rmo/content/compliance/Contact.vue";
|
||||||
import { type Permission } from "@/rmo/content/compliance/Permission.vue";
|
import { type Permission } from "@/rmo/content/compliance/Permission.vue";
|
||||||
|
|
@ -52,10 +58,12 @@ import { type Permission } from "@/rmo/content/compliance/Permission.vue";
|
||||||
export interface Compliance {
|
export interface Compliance {
|
||||||
comments: string;
|
comments: string;
|
||||||
contact: Contact;
|
contact: Contact;
|
||||||
|
id: string;
|
||||||
|
images: Image[];
|
||||||
location: Location;
|
location: Location;
|
||||||
locator: Locator;
|
locator: Locator;
|
||||||
images: Image[];
|
|
||||||
permission: Permission;
|
permission: Permission;
|
||||||
|
uri: string;
|
||||||
}
|
}
|
||||||
interface Props {
|
interface Props {
|
||||||
slug: string;
|
slug: string;
|
||||||
|
|
@ -71,27 +79,15 @@ const compliance = ref<Compliance>({
|
||||||
can_text: true,
|
can_text: true,
|
||||||
email: "",
|
email: "",
|
||||||
},
|
},
|
||||||
|
id: "",
|
||||||
images: [],
|
images: [],
|
||||||
location: {
|
location: {
|
||||||
latitude: 0,
|
latitude: 0,
|
||||||
longitude: 0,
|
longitude: 0,
|
||||||
},
|
},
|
||||||
locator: {
|
locator: {
|
||||||
address: {
|
address: new Address(),
|
||||||
country: "",
|
location: new Location(),
|
||||||
gid: "",
|
|
||||||
locality: "",
|
|
||||||
number: "",
|
|
||||||
postal_code: "",
|
|
||||||
raw: "",
|
|
||||||
region: "",
|
|
||||||
street: "",
|
|
||||||
unit: "",
|
|
||||||
},
|
|
||||||
location: {
|
|
||||||
latitude: 0,
|
|
||||||
longitude: 0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
permission: {
|
permission: {
|
||||||
access: PermissionAccess.UNSELECTED,
|
access: PermissionAccess.UNSELECTED,
|
||||||
|
|
@ -101,7 +97,9 @@ const compliance = ref<Compliance>({
|
||||||
has_dog: false,
|
has_dog: false,
|
||||||
wants_scheduled: false,
|
wants_scheduled: false,
|
||||||
},
|
},
|
||||||
|
uri: "",
|
||||||
});
|
});
|
||||||
|
const isLoading = ref<boolean>(true);
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
const report = ref<PublicReport | null>();
|
const report = ref<PublicReport | null>();
|
||||||
const district = computedAsync(async (): Promise<District | undefined> => {
|
const district = computedAsync(async (): Promise<District | undefined> => {
|
||||||
|
|
@ -110,21 +108,18 @@ const district = computedAsync(async (): Promise<District | undefined> => {
|
||||||
});
|
});
|
||||||
const storeLocal = useStoreLocal();
|
const storeLocal = useStoreLocal();
|
||||||
const storeLocation = useStoreLocation();
|
const storeLocation = useStoreLocation();
|
||||||
function doEvidence() {
|
async function createReport(client_id: string, loc?: GeolocationPosition) {
|
||||||
console.log("evidence", compliance.value);
|
|
||||||
}
|
|
||||||
function doContact() {
|
|
||||||
console.log("contact", compliance.value.contact);
|
|
||||||
}
|
|
||||||
function doLocator() {
|
|
||||||
console.log("locator done", compliance.value.locator);
|
|
||||||
}
|
|
||||||
function doPermission() {
|
|
||||||
console.log("permission", compliance.value.permission);
|
|
||||||
}
|
|
||||||
async function createReport(report_id: string, loc?: GeolocationPosition) {
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append;
|
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", {
|
const resp = await fetch("/api/rmo/compliance", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
|
|
@ -133,19 +128,52 @@ async function createReport(report_id: string, loc?: GeolocationPosition) {
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
const content = await resp.text();
|
const content = await resp.text();
|
||||||
console.error("Failed to create compliance report", resp.status, content);
|
console.error("Failed to create compliance report", resp.status, content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function doEvidence() {
|
||||||
|
console.log("evidence", compliance.value);
|
||||||
|
}
|
||||||
|
function doContact() {
|
||||||
|
console.log("contact", compliance.value.contact);
|
||||||
|
}
|
||||||
|
function doLocator() {
|
||||||
|
console.log("locator done", compliance.value.locator);
|
||||||
|
updateReport({
|
||||||
|
locator: compliance.value.locator,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function doPermission() {
|
||||||
|
console.log("permission", compliance.value.permission);
|
||||||
|
}
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
const content = await resp.text();
|
||||||
|
console.error("Failed to update compliance", resp.status, content);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const session_id = storeLocal.getSessionID();
|
const client_id = storeLocal.getClientID();
|
||||||
storeLocation
|
storeLocation
|
||||||
.get()
|
.get()
|
||||||
.then((loc: GeolocationPosition) => {
|
.then((loc: GeolocationPosition) => {
|
||||||
compliance.value.location = loc.coords;
|
compliance.value.location = loc.coords;
|
||||||
createReport(session_id, loc);
|
createReport(client_id, loc);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log("failed to get location", e);
|
console.log("failed to get location", e);
|
||||||
createReport(session_id);
|
createReport(client_id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
export const useStoreLocal = defineStore("local", () => {
|
export const useStoreLocal = defineStore("local", () => {
|
||||||
function getSessionID(): string {
|
function getClientID(): string {
|
||||||
let id = localStorage.getItem("session_id");
|
let id = localStorage.getItem("session_id");
|
||||||
if (id) {
|
if (id) {
|
||||||
return id;
|
return id;
|
||||||
|
|
@ -11,6 +11,6 @@ export const useStoreLocal = defineStore("local", () => {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
getSessionID,
|
getClientID,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,15 @@ export enum PermissionAccess {
|
||||||
}
|
}
|
||||||
export class Address {
|
export class Address {
|
||||||
constructor(
|
constructor(
|
||||||
public country: string,
|
public country: string = "",
|
||||||
public gid: string,
|
public gid: string = "",
|
||||||
public locality: string,
|
public locality: string = "",
|
||||||
public number: string,
|
public number: string = "",
|
||||||
public postal_code: string,
|
public postal_code: string = "",
|
||||||
public raw: string,
|
public raw: string = "",
|
||||||
public region: string,
|
public region: string = "",
|
||||||
public street: string,
|
public street: string = "",
|
||||||
public unit: string,
|
public unit: string = "",
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
export interface Bounds {
|
export interface Bounds {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue