Pretty all the things I missed
My laptop didn't have lefthook running. Oops.
This commit is contained in:
parent
f60bde7fd9
commit
4bbfbdb9e6
30 changed files with 490 additions and 487 deletions
|
|
@ -1,209 +1,220 @@
|
|||
<style scoped>
|
||||
.upload-widget {
|
||||
max-width: 400px;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 400px;
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
position: relative;
|
||||
border: 2px dashed #cbd5e0;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
background-color: #f7fafc;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border: 2px dashed #cbd5e0;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
background-color: #f7fafc;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: #4299e1;
|
||||
background-color: #ebf8ff;
|
||||
border-color: #4299e1;
|
||||
background-color: #ebf8ff;
|
||||
}
|
||||
|
||||
.file-input {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-input:disabled {
|
||||
cursor: not-allowed;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.upload-info {
|
||||
pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #718096;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.placeholder svg {
|
||||
margin: 0 auto 1rem;
|
||||
color: #a0aec0;
|
||||
margin: 0 auto 1rem;
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
.placeholder p {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.file-selected {
|
||||
color: #2d3748;
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
margin: 0 0 0.5rem;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
margin: 0 0 0.5rem;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
color: #718096;
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.upload-button {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background-color: #4299e1;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background-color: #4299e1;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.upload-button:hover {
|
||||
background-color: #3182ce;
|
||||
background-color: #3182ce;
|
||||
}
|
||||
|
||||
.upload-button:disabled {
|
||||
background-color: #cbd5e0;
|
||||
cursor: not-allowed;
|
||||
background-color: #cbd5e0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.upload-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background-color: #ebf8ff;
|
||||
border-radius: 6px;
|
||||
color: #2c5282;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background-color: #ebf8ff;
|
||||
border-radius: 6px;
|
||||
color: #2c5282;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid #bee3f8;
|
||||
border-top-color: #4299e1;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid #bee3f8;
|
||||
border-top-color: #4299e1;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.success-message {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background-color: #c6f6d5;
|
||||
color: #22543d;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background-color: #c6f6d5;
|
||||
color: #22543d;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background-color: #fed7d7;
|
||||
color: #742a2a;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background-color: #fed7d7;
|
||||
color: #742a2a;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="upload-widget">
|
||||
<div class="upload-area">
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept=".csv"
|
||||
@change="handleFileSelect"
|
||||
:disabled="isUploading"
|
||||
class="file-input"
|
||||
/>
|
||||
|
||||
<div class="upload-info">
|
||||
<div v-if="!selectedFile" class="placeholder">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="17 8 12 3 7 8"></polyline>
|
||||
<line x1="12" y1="3" x2="12" y2="15"></line>
|
||||
</svg>
|
||||
<p>Select a CSV file to upload</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="file-selected">
|
||||
<p class="file-name">{{ selectedFile.name }}</p>
|
||||
<p class="file-size">{{ formatFileSize(selectedFile.size) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload-widget">
|
||||
<div class="upload-area">
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept=".csv"
|
||||
@change="handleFileSelect"
|
||||
:disabled="isUploading"
|
||||
class="file-input"
|
||||
/>
|
||||
|
||||
<button
|
||||
v-if="selectedFile && !isUploading"
|
||||
@click="uploadFile"
|
||||
class="upload-button"
|
||||
:disabled="isUploading"
|
||||
>
|
||||
Upload File
|
||||
</button>
|
||||
<div class="upload-info">
|
||||
<div v-if="!selectedFile" class="placeholder">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="17 8 12 3 7 8"></polyline>
|
||||
<line x1="12" y1="3" x2="12" y2="15"></line>
|
||||
</svg>
|
||||
<p>Select a CSV file to upload</p>
|
||||
</div>
|
||||
|
||||
<div v-if="isUploading" class="upload-status">
|
||||
<div class="spinner"></div>
|
||||
<p>Uploading...</p>
|
||||
</div>
|
||||
<div v-else class="file-selected">
|
||||
<p class="file-name">{{ selectedFile.name }}</p>
|
||||
<p class="file-size">{{ formatFileSize(selectedFile.size) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="uploadSuccess" class="success-message">
|
||||
✓ File uploaded successfully!
|
||||
</div>
|
||||
<button
|
||||
v-if="selectedFile && !isUploading"
|
||||
@click="uploadFile"
|
||||
class="upload-button"
|
||||
:disabled="isUploading"
|
||||
>
|
||||
Upload File
|
||||
</button>
|
||||
|
||||
<div v-if="errorMessage" class="error-message">
|
||||
✗ {{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isUploading" class="upload-status">
|
||||
<div class="spinner"></div>
|
||||
<p>Uploading...</p>
|
||||
</div>
|
||||
|
||||
<div v-if="uploadSuccess" class="success-message">
|
||||
✓ File uploaded successfully!
|
||||
</div>
|
||||
|
||||
<div v-if="errorMessage" class="error-message">✗ {{ errorMessage }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ref } from "vue";
|
||||
|
||||
interface Props {
|
||||
uploadUrl: string;
|
||||
headers?: Record<string, string>;
|
||||
additionalData?: Record<string, string>;
|
||||
uploadUrl: string;
|
||||
headers?: Record<string, string>;
|
||||
additionalData?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'doError', error: Error): void;
|
||||
(e: 'doFileSelected', file: File): void;
|
||||
(e: 'doSuccess', response: any): void;
|
||||
(e: "doError", error: Error): void;
|
||||
(e: "doFileSelected", file: File): void;
|
||||
(e: "doSuccess", response: any): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
headers: () => ({}),
|
||||
additionalData: () => ({})
|
||||
headers: () => ({}),
|
||||
additionalData: () => ({}),
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
|
@ -212,89 +223,89 @@ const fileInput = ref<HTMLInputElement | null>(null);
|
|||
const selectedFile = ref<File | null>(null);
|
||||
const isUploading = ref(false);
|
||||
const uploadSuccess = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const errorMessage = ref("");
|
||||
|
||||
const handleFileSelect = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const file = target.files?.[0];
|
||||
|
||||
if (file) {
|
||||
// Validate it's a CSV file
|
||||
if (!file.name.toLowerCase().endsWith('.csv')) {
|
||||
errorMessage.value = 'Please select a valid CSV file';
|
||||
selectedFile.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
selectedFile.value = file;
|
||||
errorMessage.value = '';
|
||||
uploadSuccess.value = false;
|
||||
emit('doFileSelected', file);
|
||||
}
|
||||
const target = event.target as HTMLInputElement;
|
||||
const file = target.files?.[0];
|
||||
|
||||
if (file) {
|
||||
// Validate it's a CSV file
|
||||
if (!file.name.toLowerCase().endsWith(".csv")) {
|
||||
errorMessage.value = "Please select a valid CSV file";
|
||||
selectedFile.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
selectedFile.value = file;
|
||||
errorMessage.value = "";
|
||||
uploadSuccess.value = false;
|
||||
emit("doFileSelected", file);
|
||||
}
|
||||
};
|
||||
|
||||
const uploadFile = async () => {
|
||||
if (!selectedFile.value) return;
|
||||
if (!selectedFile.value) return;
|
||||
|
||||
isUploading.value = true;
|
||||
errorMessage.value = '';
|
||||
uploadSuccess.value = false;
|
||||
isUploading.value = true;
|
||||
errorMessage.value = "";
|
||||
uploadSuccess.value = false;
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile.value);
|
||||
|
||||
// Add any additional data to the form
|
||||
Object.entries(props.additionalData).forEach(([key, value]) => {
|
||||
formData.append(key, value);
|
||||
});
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("file", selectedFile.value);
|
||||
|
||||
const response = await fetch(props.uploadUrl, {
|
||||
body: formData,
|
||||
headers: {
|
||||
...props.headers,
|
||||
// Don't set Content-Type - let browser set it with boundary
|
||||
},
|
||||
method: 'POST',
|
||||
});
|
||||
// Add any additional data to the form
|
||||
Object.entries(props.additionalData).forEach(([key, value]) => {
|
||||
formData.append(key, value);
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload failed: ${response.statusText}`);
|
||||
}
|
||||
const response = await fetch(props.uploadUrl, {
|
||||
body: formData,
|
||||
headers: {
|
||||
...props.headers,
|
||||
// Don't set Content-Type - let browser set it with boundary
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
uploadSuccess.value = true;
|
||||
emit('doSuccess', data);
|
||||
|
||||
resetUpload();
|
||||
} catch (error) {
|
||||
errorMessage.value = error instanceof Error ? error.message : 'Upload failed';
|
||||
emit('doError', error as Error);
|
||||
} finally {
|
||||
isUploading.value = false;
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
uploadSuccess.value = true;
|
||||
emit("doSuccess", data);
|
||||
|
||||
resetUpload();
|
||||
} catch (error) {
|
||||
errorMessage.value =
|
||||
error instanceof Error ? error.message : "Upload failed";
|
||||
emit("doError", error as Error);
|
||||
} finally {
|
||||
isUploading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const resetUpload = () => {
|
||||
selectedFile.value = null;
|
||||
uploadSuccess.value = false;
|
||||
errorMessage.value = '';
|
||||
if (fileInput.value) {
|
||||
fileInput.value.value = '';
|
||||
}
|
||||
selectedFile.value = null;
|
||||
uploadSuccess.value = false;
|
||||
errorMessage.value = "";
|
||||
if (fileInput.value) {
|
||||
fileInput.value.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024;
|
||||
const sizes = ["Bytes", "KB", "MB", "GB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
||||
};
|
||||
|
||||
// Expose methods for parent component
|
||||
defineExpose({
|
||||
resetUpload
|
||||
resetUpload,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,10 @@
|
|||
</div>
|
||||
|
||||
<div v-if="selectedCommunication" class="h-100 d-flex flex-column">
|
||||
<PublicreportCard :report="selectedCommunication.public_report" @viewImage="openPhotoViewer" />
|
||||
<PublicreportCard
|
||||
:report="selectedCommunication.public_report"
|
||||
@viewImage="openPhotoViewer"
|
||||
/>
|
||||
<!-- Report Details -->
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
<template>
|
||||
<p>A flyover pool</p>
|
||||
<MapProxiedArcgisTile
|
||||
id="tile-map"
|
||||
:latitude="pool.location.latitude"
|
||||
:longitude="pool.location.longitude"
|
||||
:markers="tileMapMarkers"
|
||||
:organizationId="user.organization.id"
|
||||
:tegola="user.urls.tegola" />
|
||||
id="tile-map"
|
||||
:latitude="pool.location.latitude"
|
||||
:longitude="pool.location.longitude"
|
||||
:markers="tileMapMarkers"
|
||||
:organizationId="user.organization.id"
|
||||
:tegola="user.urls.tegola"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
|||
|
|
@ -15,18 +15,13 @@
|
|||
</div>
|
||||
<div v-else>
|
||||
<h1>Map failed to load</h1>
|
||||
<p>{{error}}</p>
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
import {
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import { Bounds, Marker } from "@/types";
|
||||
import maplibregl from "maplibre-gl";
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
</div>
|
||||
<div v-else>
|
||||
<h1>Map failed to load</h1>
|
||||
<p>{{error}}</p>
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ import { Point } from "@/types";
|
|||
import maplibregl from "maplibre-gl";
|
||||
|
||||
interface Emits {
|
||||
(e: "map-click", latitude: Number, longitude: Number): void
|
||||
(e: "map-click", latitude: Number, longitude: Number): void;
|
||||
}
|
||||
interface Props {
|
||||
location: Point;
|
||||
|
|
@ -40,8 +40,8 @@ const props = defineProps<Props>();
|
|||
const error = ref<string | null>(null);
|
||||
const mapContainer = ref<HTMLElement | null>(null);
|
||||
const map = ref<maplibregl.Map | null>(null);
|
||||
const markerInstances = ref<Map<string, maplibrgl.Marker>>(new Map())
|
||||
const markers = ref<Map<string, maplibrgl.Marker>>(new Map())
|
||||
const markerInstances = ref<Map<string, maplibrgl.Marker>>(new Map());
|
||||
const markers = ref<Map<string, maplibrgl.Marker>>(new Map());
|
||||
|
||||
// Watch for latitude/longitude changes
|
||||
watch(
|
||||
|
|
@ -108,7 +108,7 @@ const initializeMap = () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.error("hey dummy", e);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,13 +47,22 @@
|
|||
|
||||
<div class="mb-3">
|
||||
<div class="text-muted small mb-2">Lead → Field Assignment</div>
|
||||
<button class="btn btn-outline-success tool-button" @click="emit('doCreateProposedAssignment')">
|
||||
<button
|
||||
class="btn btn-outline-success tool-button"
|
||||
@click="emit('doCreateProposedAssignment')"
|
||||
>
|
||||
Create Proposed Assignment
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary tool-button" @click="emit('doAddLeadsToAssignment')">
|
||||
<button
|
||||
class="btn btn-outline-secondary tool-button"
|
||||
@click="emit('doAddLeadsToAssignment')"
|
||||
>
|
||||
Add Leads to Existing Assignment
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary tool-button" @click="emit('doSplitLead')">
|
||||
<button
|
||||
class="btn btn-outline-secondary tool-button"
|
||||
@click="emit('doSplitLead')"
|
||||
>
|
||||
Split Lead
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -62,11 +71,22 @@
|
|||
|
||||
<div class="mb-3">
|
||||
<div class="text-muted small mb-2">Assignment Controls</div>
|
||||
<button class="btn btn-outline-dark tool-button" @click="emit('doSetPriority')">Set Priority</button>
|
||||
<button class="btn btn-outline-dark tool-button" @click="emit('doEstimateEffort')">
|
||||
<button
|
||||
class="btn btn-outline-dark tool-button"
|
||||
@click="emit('doSetPriority')"
|
||||
>
|
||||
Set Priority
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-outline-dark tool-button"
|
||||
@click="emit('doEstimateEffort')"
|
||||
>
|
||||
Estimate Effort
|
||||
</button>
|
||||
<button class="btn btn-outline-dark tool-button" @click="emit('doSendToOperations')">
|
||||
<button
|
||||
class="btn btn-outline-dark tool-button"
|
||||
@click="emit('doSendToOperations')"
|
||||
>
|
||||
Send to Operations
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -42,13 +42,10 @@
|
|||
Click signals from the left panel to select them
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-2"
|
||||
v-show="selectedSignals.length > 0"
|
||||
>
|
||||
<div v-for="signal in selectedSignals" :key="signal.id">
|
||||
<PlanningColumnDetailEntry :signal="signal"/>
|
||||
</div>
|
||||
<div class="mt-2" v-show="selectedSignals.length > 0">
|
||||
<div v-for="signal in selectedSignals" :key="signal.id">
|
||||
<PlanningColumnDetailEntry :signal="signal" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -123,18 +120,24 @@ const selectedSignalLocation = () => {
|
|||
return accumulator;
|
||||
}, null);
|
||||
const loc = first_pool?.location;
|
||||
return loc || {
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
}
|
||||
return (
|
||||
loc || {
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
}
|
||||
);
|
||||
};
|
||||
const showMapTile = () => {
|
||||
return selectedSignalLocation() && props.selectedSignals.value
|
||||
.values()
|
||||
.reduce(
|
||||
(accumulator, current) => accumulator || current.type === "flyover pool",
|
||||
false,
|
||||
);
|
||||
return (
|
||||
selectedSignalLocation() &&
|
||||
props.selectedSignals.value
|
||||
.values()
|
||||
.reduce(
|
||||
(accumulator, current) =>
|
||||
accumulator || current.type === "flyover pool",
|
||||
false,
|
||||
)
|
||||
);
|
||||
};
|
||||
const updateSignalLocation = (event) => {
|
||||
const signalId = event.detail.signalId;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
<TimeRelative :time="signal.created"></TimeRelative>
|
||||
<p>{{ shortAddress(signal.address) }}</p>
|
||||
<div v-if="signal.type == 'flyover pool' && signal.pool">
|
||||
<FlyoverPoolCard :pool="signal.pool"/>
|
||||
<FlyoverPoolCard :pool="signal.pool" />
|
||||
</div>
|
||||
<div v-else-if="signal.type == 'publicreport nuisance'">
|
||||
<PublicreportCard :report="signal.report"/>
|
||||
<PublicreportCard :report="signal.report" />
|
||||
</div>
|
||||
<div v-else-if="signal.type == 'publicreport water'">
|
||||
<PublicreportCard :report="signal.report"/>
|
||||
<PublicreportCard :report="signal.report" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,10 @@
|
|||
:class="{ selected: isSelected(signal.id) }"
|
||||
@click="toggleSignal(signal)"
|
||||
>
|
||||
<PlanningColumnListEntry :selected="selectedSignalIDs.has(signal.id)" :signal="signal"/>
|
||||
<PlanningColumnListEntry
|
||||
:selected="selectedSignalIDs.has(signal.id)"
|
||||
:signal="signal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { shortAddress } from "../format";
|
|||
interface Props {
|
||||
selected: boolean;
|
||||
signal: Signal;
|
||||
};
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
function icon(signal: Signal): string {
|
||||
if (signal.type == "flyover pool") {
|
||||
|
|
@ -34,7 +34,7 @@ function icon(signal: Signal): string {
|
|||
return "bi-mosquito";
|
||||
} else if (signal.type == "publicreport water") {
|
||||
return "bi-water";
|
||||
} else {
|
||||
} else {
|
||||
return "bi-mosquito";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,24 +16,16 @@
|
|||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<div>
|
||||
<h5 class="mb-1">
|
||||
<span
|
||||
v-if="
|
||||
report.type === 'nuisance'
|
||||
"
|
||||
>
|
||||
<span v-if="report.type === 'nuisance'">
|
||||
<i class="bi bi-mosquito icon-nuisance"></i>
|
||||
Nuisance Report
|
||||
</span>
|
||||
<span
|
||||
v-if="report.type === 'water'"
|
||||
>
|
||||
<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
|
||||
>
|
||||
<small class="text-muted">Report ID: #{{ report.public_id }}</small>
|
||||
</div>
|
||||
<span class="badge bg-secondary">
|
||||
<TimeRelative :time="report.created" />
|
||||
|
|
@ -49,11 +41,7 @@
|
|||
<i class="bi bi-geo-alt"></i> Address
|
||||
</label>
|
||||
<div class="fw-medium">
|
||||
{{
|
||||
formatAddress(
|
||||
report.address,
|
||||
)
|
||||
}}
|
||||
{{ formatAddress(report.address) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
|
|
@ -61,25 +49,18 @@
|
|||
<i class="bi bi-person"></i> Reporter Name
|
||||
</label>
|
||||
<div class="fw-medium">
|
||||
{{
|
||||
report.reporter.name ||
|
||||
"not given"
|
||||
}}
|
||||
{{ report.reporter.name || "not given" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label
|
||||
v-if="
|
||||
report.reporter.has_email
|
||||
"
|
||||
v-if="report.reporter.has_email"
|
||||
class="form-label text-muted small mb-0"
|
||||
>
|
||||
<i class="bi bi-envelope"></i>
|
||||
</label>
|
||||
<label
|
||||
v-if="
|
||||
report.reporter.has_phone
|
||||
"
|
||||
v-if="report.reporter.has_phone"
|
||||
class="form-label text-muted small mb-0"
|
||||
>
|
||||
<i class="bi bi-phone"></i>
|
||||
|
|
@ -126,9 +107,7 @@
|
|||
<div>
|
||||
<ul>
|
||||
<li v-if="report.nuisance?.is_location_backyard">Backyard</li>
|
||||
<li v-if="report.nuisance?.is_location_frontyard">
|
||||
Frontyard
|
||||
</li>
|
||||
<li v-if="report.nuisance?.is_location_frontyard">Frontyard</li>
|
||||
<li v-if="report.nuisance?.is_location_garden">Garden</li>
|
||||
<li v-if="report.nuisance?.is_location_other">Other</li>
|
||||
<li v-if="report.nuisance?.is_location_pool">Pool</li>
|
||||
|
|
@ -245,9 +224,7 @@
|
|||
>
|
||||
<i
|
||||
class="bi"
|
||||
:class="
|
||||
report.water?.has_pupae ? 'bi-check-circle' : 'bi-circle'
|
||||
"
|
||||
:class="report.water?.has_pupae ? 'bi-check-circle' : 'bi-circle'"
|
||||
></i>
|
||||
Pupae
|
||||
</span>
|
||||
|
|
@ -259,9 +236,7 @@
|
|||
>
|
||||
<i
|
||||
class="bi"
|
||||
:class="
|
||||
report.water?.has_adult ? 'bi-check-circle' : 'bi-circle'
|
||||
"
|
||||
:class="report.water?.has_adult ? 'bi-check-circle' : 'bi-circle'"
|
||||
></i>
|
||||
Adult Mosquitoes
|
||||
</span>
|
||||
|
|
@ -311,10 +286,7 @@
|
|||
</div>
|
||||
<div class="card-body">
|
||||
<div
|
||||
v-if="
|
||||
report.images &&
|
||||
report.images.length > 0
|
||||
"
|
||||
v-if="report.images && report.images.length > 0"
|
||||
class="d-flex flex-wrap gap-2"
|
||||
>
|
||||
<img
|
||||
|
|
@ -327,10 +299,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
!report.images ||
|
||||
report.images.length === 0
|
||||
"
|
||||
v-if="!report.images || report.images.length === 0"
|
||||
class="text-muted text-center py-3"
|
||||
>
|
||||
<i class="bi bi-camera-slash fs-4"></i>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue