Add ability to select items and display in detail view

This commit is contained in:
Eli Ribble 2026-03-23 17:14:14 -07:00
parent e0a586b311
commit fb853a2bd3
No known key found for this signature in database
5 changed files with 65 additions and 44 deletions

View file

@ -18,7 +18,7 @@
<div class="text-muted small mb-2">Signal Lead</div>
<button
class="btn btn-outline-primary tool-button"
:disabled="selectedSignals.length === 0 || creating"
:disabled="selectedSignalIDs.size === 0 || creating"
@click="createLead()"
>
<span v-if="!creating">Create New Lead from Selection</span>
@ -29,13 +29,13 @@
</button>
<button
class="btn btn-outline-secondary tool-button"
:disabled="selectedSignals.length === 0"
:disabled="selectedSignalIDs.size === 0"
>
Add Signals to Existing Lead
</button>
<button
class="btn btn-outline-secondary tool-button"
:disabled="selectedSignals.length === 0"
:disabled="selectedSignalIDs.size === 0"
@click="markAsAddressed()"
>
Mark Signal as Addressed
@ -75,7 +75,7 @@
<script setup lang="ts">
interface Props {
creating: boolean;
selectedSignals: Set<int>;
selectedSignalIDs: Set<int>;
}
const props = defineProps<Props>();
</script>

View file

@ -48,28 +48,7 @@
>
<tbody>
<tr v-for="signal in selectedSignals" :key="signal.id">
<td>
<button
@click="toggleSignal(signal)"
class="btn btn-sm btn-link text-danger p-0 ms-1"
style="font-size: 0.7rem"
>
<i class="bi bi-x"></i>
</button>
</td>
<td>
<span v-if="signal.type === 'flyover pool'">Pool</span>
<span v-else-if="signal.type === 'publicreport nuisance'"
>Nuisance</span
>
<span v-else-if="signal.type === 'publicreport water'"
>Water</span
>
</td>
<td>
<TimeRelative :time="signal.created"></TimeRelative>
</td>
<td>{{ shortAddress(signal.address) }}</td>
<PlanningColumnDetailEntry :signal="signal"/>
</tr>
</tbody>
</table>
@ -107,11 +86,12 @@ import MapMultipoint from "./MapMultipoint.vue";
import MapProxiedArcgisTile from "./MapProxiedArcgisTile.vue";
import { shortAddress } from "../format";
import TimeRelative from "./TimeRelative.vue";
import PlanningColumnDetailEntry from "./PlanningColumnDetailEntry.vue";
import { useUserStore } from "../store/user";
interface Props {
markers: Marker[];
selectedSignals: Set<int>;
selectedSignals: Array<Signal>;
}
const props = defineProps<Props>();
const user = useUserStore();

View file

@ -0,0 +1,33 @@
<template>
<td>
<button
@click="toggleSignal(signal)"
class="btn btn-sm btn-link text-danger p-0 ms-1"
style="font-size: 0.7rem"
>
<i class="bi bi-x"></i>
</button>
</td>
<td>
<span v-if="signal.type === 'flyover pool'">Pool</span>
<span v-else-if="signal.type === 'publicreport nuisance'"
>Nuisance</span
>
<span v-else-if="signal.type === 'publicreport water'"
>Water</span
>
</td>
<td>
<TimeRelative :time="signal.created"></TimeRelative>
</td>
<td>{{ shortAddress(signal.address) }}</td>
</template>
<script setup lang="ts">
import { shortAddress } from "../format";
import TimeRelative from "@/components/TimeRelative.vue";
interface Props {
signal: Signal;
}
const props = defineProps<Props>();
</script>

View file

@ -118,13 +118,13 @@
<div class="mb-3">
<div class="fw-semibold mb-2">
Signals
<span class="badge bg-primary" v-show="selectedSignals.size > 0">
{{ selectedSignals.size }}
<span class="badge bg-primary" v-show="selectedSignalIDs.size > 0">
{{ selectedSignalIDs.size }}
</span>
</div>
<div
v-if="signals != null && signals.length === 0"
v-if="signals != null && signals.length == 0"
class="text-muted small fst-italic"
>
No signals found
@ -209,7 +209,7 @@ interface Props {
leads: Lead[] | null;
loading: boolean;
planFollowups: Followup[] | null;
selectedSignals: Set<int>;
selectedSignalIDs: Set<int>;
signals: Signal[] | null;
}
const emit = defineEmits<Emits>();
@ -220,10 +220,10 @@ const filters = ref({
sort: "newest",
});
const isSelected = (id) => {
return props.selectedSignals.values().some((s) => s.id === id);
return props.selectedSignalIDs.values().some((s) => s.id === id);
};
const toggleSignal = (signal: int) => {
if (props.selectedSignals.has(signal)) {
if (props.selectedSignalIDs.has(signal)) {
emit("signalDeselect", signal);
} else {
emit("signalSelect", signal);

View file

@ -25,7 +25,7 @@
:loading="loading"
:planFollowups="planFollowups"
@refresh="refresh"
:selectedSignals="selectedSignals"
:selectedSignalIDs="selectedSignalIDs"
@signalDeselect="signalDeselect"
@signalSelect="signalSelect"
:signals="signal.all"
@ -35,12 +35,13 @@
<PlanningColumnDetail
:markers="markers"
:selectedSignals="selectedSignals"
:signals="signal.all"
/>
</template>
<template #right>
<PlanningColumnAction
:creating="creating"
:selectedSignals="selectedSignals"
:selectedSignalIDs="selectedSignalIDs"
/>
</template>
</ThreeColumn>
@ -70,7 +71,7 @@ const leads = ref([]);
const loading = ref(false);
const planFollowups = ref([]);
const poolLocations = ref({});
const selectedSignals = ref(new Set([]));
const selectedSignalIDs = ref(new Set<int>([]));
const signal = useSignalStore();
const user = useUserStore();
@ -100,6 +101,13 @@ const getBoundingBox = (points) => {
const markers = computed(() => {
return [];
});
const selectedSignals = computed(() => {
if (selectedSignalIDs.value.size == 0 || signal.all == null) {
return [];
}
const result = signal.all.filter((s) => selectedSignalIDs.value.has(s));
return result;
});
const updateMap = (signals) => {
const locations = signals.map((s) => s.location);
const markers = locations.map((l) =>
@ -151,11 +159,11 @@ const loadPlanFollowups = async () => {
};
const clearSelection = () => {
selectedSignals.value.clear();
selectedSignalIDs.value.clear();
};
const createLead = async () => {
if (selectedSignals.value.size === 0) return;
if (selectedSignalIDs.value.size === 0) return;
creating.value = true;
@ -167,7 +175,7 @@ const createLead = async () => {
},
body: JSON.stringify({
pool_locations: poolLocations.value,
signal_ids: selectedSignals.value.map((s) => s.id),
signal_ids: selectedSignalIDs,
}),
});
@ -191,7 +199,7 @@ const createLead = async () => {
};
const markAsAddressed = async () => {
if (selectedSignals.value.size === 0) return;
if (selectedSignalIDs.value.size === 0) return;
try {
const response = await fetch(`${apiBase.value}/signal/mark-addressed`, {
@ -200,7 +208,7 @@ const markAsAddressed = async () => {
"Content-Type": "application/json",
},
body: JSON.stringify({
signal_ids: selectedSignals.value.map((s) => s.id),
signal_ids: selectedSignalIDs,
}),
});
@ -209,7 +217,7 @@ const markAsAddressed = async () => {
}
signals.value = signals.value.filter(
(signal) => !selectedSignals.value.some((s) => s.id === signal.id),
(signal) => !selectedSignalIDs.value.has(s.id),
);
clearSelection();
@ -223,10 +231,10 @@ const refresh = () => {
loadData();
};
const signalDeselect = (id: int) => {
selectedSignals.value.delete(id);
selectedSignalIDs.value.delete(id);
};
const signalSelect = (id: int) => {
selectedSignals.value.add(id);
selectedSignalIDs.value.add(id);
};
// Lifecycle
onMounted(() => {