Add status and source filters to communications display

This commit is contained in:
Eli Ribble 2026-05-12 00:19:19 +00:00
parent 9be95763e2
commit fa012bebca
No known key found for this signature in database

View file

@ -1,20 +1,126 @@
<style scoped>
.filters-section {
font-size: 0.875rem;
}
.btn-group-sm .btn {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
}
.form-label.small {
margin-bottom: 0.25rem;
color: #6c757d;
}
</style>
<template> <template>
<div class="card shadow-sm h-100 reports-list"> <div class="card shadow-sm h-100 reports-list">
<div class="card-header bg-light pane-header"> <div class="card-header bg-light pane-header">
<div class="input-group input-group-sm"> <!-- Filter Section -->
<span class="input-group-text"><i class="bi bi-search"></i></span> <div class="filters-section">
<!-- Status Filter -->
<div class="mb-2">
<label class="form-label small fw-bold mb-1">Status</label>
<div class="btn-group btn-group-sm d-flex" role="group">
<input <input
type="text" type="checkbox"
class="form-control" class="btn-check"
placeholder="Filter reports..." id="status-new"
v-model="searchFilter" v-model="statusFilters.new"
autocomplete="off"
/> />
<label class="btn btn-outline-success" for="status-new">New</label>
<input
type="checkbox"
class="btn-check"
id="status-opened"
v-model="statusFilters.opened"
autocomplete="off"
/>
<label class="btn btn-outline-primary" for="status-opened"
>Opened</label
>
<input
type="checkbox"
class="btn-check"
id="status-pending"
v-model="statusFilters.pending"
autocomplete="off"
/>
<label class="btn btn-outline-warning" for="status-pending"
>Pending</label
>
<input
type="checkbox"
class="btn-check"
id="status-closed"
v-model="statusFilters.closed"
autocomplete="off"
/>
<label class="btn btn-outline-secondary" for="status-closed"
>Closed</label
>
</div> </div>
</div> </div>
<div class="card-body scroll-pane">
<div class="mb-3"> <!-- Source Filter -->
<div class="mb-2">
<label class="form-label small fw-bold mb-1">Source</label>
<div class="btn-group btn-group-sm d-flex" role="group">
<button <button
class="btn btn-sm" class="btn"
:class="
sourceFilter === 'all' ? 'btn-primary' : 'btn-outline-secondary'
"
@click="sourceFilter = 'all'"
>
All
</button>
<button
class="btn"
:class="
sourceFilter === 'email'
? 'btn-primary'
: 'btn-outline-secondary'
"
@click="sourceFilter = 'email'"
>
<i class="bi bi-envelope"></i> Email
</button>
<button
class="btn"
:class="
sourceFilter === 'text'
? 'btn-primary'
: 'btn-outline-secondary'
"
@click="sourceFilter = 'text'"
>
<i class="bi bi-chat-dots"></i> Text
</button>
<button
class="btn"
:class="
sourceFilter === 'publicreport'
? 'btn-primary'
: 'btn-outline-secondary'
"
@click="sourceFilter = 'publicreport'"
>
<i class="bi bi-file-text"></i> Form
</button>
</div>
</div>
<!-- Type Filter -->
<div class="mb-2" v-if="sourceFilter == 'publicreport'">
<label class="form-label small fw-bold mb-1">Type</label>
<div class="btn-group btn-group-sm d-flex" role="group">
<button
class="btn"
:class=" :class="
typeFilter === 'all' ? 'btn-primary' : 'btn-outline-secondary' typeFilter === 'all' ? 'btn-primary' : 'btn-outline-secondary'
" "
@ -23,29 +129,56 @@
All All
</button> </button>
<button <button
class="btn btn-sm" class="btn"
:class=" :class="
typeFilter === 'nuisance' ? 'btn-danger' : 'btn-outline-secondary' typeFilter === 'publicreport.compliance'
? 'btn-success'
: 'btn-outline-secondary'
" "
@click="typeFilter = 'nuisance'" @click="typeFilter = 'publicreport.compliance'"
> >
<i class="bi bi-mosquito"></i>Nuisance <i class="bi bi-check-circle"></i> Compliance
</button> </button>
<button <button
class="btn btn-sm" class="btn"
:class="typeFilter === 'water' ? 'btn-info' : 'btn-outline-secondary'" :class="
@click="typeFilter = 'water'" typeFilter === 'publicreport.nuisance'
? 'btn-danger'
: 'btn-outline-secondary'
"
@click="typeFilter = 'publicreport.nuisance'"
>
<i class="bi bi-mosquito"></i> Nuisance
</button>
<button
class="btn"
:class="
typeFilter === 'publicreport.water'
? 'btn-info'
: 'btn-outline-secondary'
"
@click="typeFilter = 'publicreport.water'"
> >
<i class="bi bi-droplet"></i> Water <i class="bi bi-droplet"></i> Water
</button> </button>
</div> </div>
</div>
<!-- Active Filters Summary -->
<div class="small text-muted" v-if="activeFilterCount > 0">
<i class="bi bi-funnel"></i> {{ filteredCommunications.length }} of
{{ props.all?.length || 0 }} reports
</div>
</div>
</div>
<div class="card-body scroll-pane">
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<div class="loading list-group-item" v-if="loading || all == null"> <div class="loading list-group-item" v-if="loading || all == null">
Loading... Loading...
</div> </div>
<div <div
v-else-if="all.length > 0" v-else-if="filteredCommunications.length > 0"
v-for="comm in filteredCommunications" v-for="comm in filteredCommunications"
:key="comm.id" :key="comm.id"
> >
@ -55,15 +188,14 @@
:isSelected="selectedID == comm.id" :isSelected="selectedID == comm.id"
/> />
</div> </div>
</div> <div v-else class="list-group-item text-center text-muted p-4">
</div>
<div
v-if="filteredCommunications.length === 0"
class="text-center text-muted p-4"
>
<i class="bi bi-inbox fs-1"></i> <i class="bi bi-inbox fs-1"></i>
<p class="mt-2">No reports found</p> <p class="mt-2">No reports match the current filters</p>
<button class="btn btn-sm btn-outline-primary" @click="resetFilters">
Reset Filters
</button>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -84,8 +216,8 @@ interface Emits {
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
const handleClick = (id: string) => { const handleClick = (id: string) => {
if (props.selectedID == undefined) { if (props.selectedID == undefined) {
emit("select", id); emit("select", id);
@ -95,23 +227,77 @@ const handleClick = (id: string) => {
emit("select", id); emit("select", id);
} }
}; };
// Filter state
const searchFilter = ref<string>(""); const searchFilter = ref<string>("");
const typeFilter = ref<string>("all"); const typeFilter = ref<string>("all");
const sourceFilter = ref<string>("all");
const statusFilters = ref({
new: true,
opened: true,
pending: false,
closed: false,
});
function selectCommunication(communication: Communication) { // Reset filters to default
// Emit both events - one for general use, one for v-model const resetFilters = () => {
console.log("selected", communication); searchFilter.value = "";
emit("select", communication.id); typeFilter.value = "all";
//emit("update:selectedItem", communication); sourceFilter.value = "all";
//messageText.value = ""; statusFilters.value = {
//updateMap(); new: true,
} opened: true,
pending: false,
closed: false,
};
};
// Computed properties // Count active filters
const activeFilterCount = computed(() => {
let count = 0;
if (typeFilter.value !== "all") count++;
if (sourceFilter.value !== "all") count++;
if (searchFilter.value.length > 0) count++;
return count;
});
// Filtered communications
const filteredCommunications = computed((): Communication[] => { const filteredCommunications = computed((): Communication[] => {
if (props.all == null) { if (props.all == null) {
return []; return [];
} }
return props.all;
return props.all.filter((comm) => {
// Status filter
const selectedStatuses = Object.entries(statusFilters.value)
.filter(([_, isSelected]) => isSelected)
.map(([status]) => status);
if (
selectedStatuses.length > 0 &&
!selectedStatuses.includes(comm.status)
) {
return false;
}
// Source filter
if (
sourceFilter.value !== "all" &&
!comm.type.startsWith(sourceFilter.value)
) {
return false;
}
// Type filter
if (
sourceFilter.value === "publicreport" &&
typeFilter.value !== "all" &&
comm.type !== typeFilter.value
) {
return false;
}
return true;
});
}); });
</script> </script>