Add status and source filters to communications display
This commit is contained in:
parent
9be95763e2
commit
fa012bebca
1 changed files with 242 additions and 56 deletions
|
|
@ -1,51 +1,184 @@
|
||||||
|
<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">
|
||||||
<input
|
<!-- Status Filter -->
|
||||||
type="text"
|
<div class="mb-2">
|
||||||
class="form-control"
|
<label class="form-label small fw-bold mb-1">Status</label>
|
||||||
placeholder="Filter reports..."
|
<div class="btn-group btn-group-sm d-flex" role="group">
|
||||||
v-model="searchFilter"
|
<input
|
||||||
/>
|
type="checkbox"
|
||||||
|
class="btn-check"
|
||||||
|
id="status-new"
|
||||||
|
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>
|
||||||
|
|
||||||
|
<!-- 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
|
||||||
|
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="
|
||||||
|
typeFilter === 'all' ? 'btn-primary' : 'btn-outline-secondary'
|
||||||
|
"
|
||||||
|
@click="typeFilter = 'all'"
|
||||||
|
>
|
||||||
|
All
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
:class="
|
||||||
|
typeFilter === 'publicreport.compliance'
|
||||||
|
? 'btn-success'
|
||||||
|
: 'btn-outline-secondary'
|
||||||
|
"
|
||||||
|
@click="typeFilter = 'publicreport.compliance'"
|
||||||
|
>
|
||||||
|
<i class="bi bi-check-circle"></i> Compliance
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
:class="
|
||||||
|
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
|
||||||
|
</button>
|
||||||
|
</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>
|
</div>
|
||||||
<div class="card-body scroll-pane">
|
|
||||||
<div class="mb-3">
|
|
||||||
<button
|
|
||||||
class="btn btn-sm"
|
|
||||||
:class="
|
|
||||||
typeFilter === 'all' ? 'btn-primary' : 'btn-outline-secondary'
|
|
||||||
"
|
|
||||||
@click="typeFilter = 'all'"
|
|
||||||
>
|
|
||||||
All
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm"
|
|
||||||
:class="
|
|
||||||
typeFilter === 'nuisance' ? 'btn-danger' : 'btn-outline-secondary'
|
|
||||||
"
|
|
||||||
@click="typeFilter = 'nuisance'"
|
|
||||||
>
|
|
||||||
<i class="bi bi-mosquito"></i>Nuisance
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm"
|
|
||||||
:class="typeFilter === 'water' ? 'btn-info' : 'btn-outline-secondary'"
|
|
||||||
@click="typeFilter = 'water'"
|
|
||||||
>
|
|
||||||
<i class="bi bi-droplet"></i> Water
|
|
||||||
</button>
|
|
||||||
</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,16 +188,15 @@
|
||||||
:isSelected="selectedID == comm.id"
|
:isSelected="selectedID == comm.id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="list-group-item text-center text-muted p-4">
|
||||||
|
<i class="bi bi-inbox fs-1"></i>
|
||||||
|
<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
|
|
||||||
v-if="filteredCommunications.length === 0"
|
|
||||||
class="text-center text-muted p-4"
|
|
||||||
>
|
|
||||||
<i class="bi bi-inbox fs-1"></i>
|
|
||||||
<p class="mt-2">No reports found</p>
|
|
||||||
</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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue