Skip to content

Commit 131e986

Browse files
committed
Adding more filters
Fixed icons to be svg
1 parent e8be495 commit 131e986

File tree

9 files changed

+217
-84
lines changed

9 files changed

+217
-84
lines changed
Lines changed: 8 additions & 0 deletions
Loading
Lines changed: 6 additions & 0 deletions
Loading
Lines changed: 7 additions & 0 deletions
Loading
Lines changed: 6 additions & 0 deletions
Loading
Lines changed: 6 additions & 0 deletions
Loading
Lines changed: 6 additions & 0 deletions
Loading

src/Frontend/src/components/audit/AuditList.vue

Lines changed: 67 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,43 @@
22
import routeLinks from "@/router/routeLinks";
33
import { useAuditStore } from "@/stores/AuditStore";
44
import { storeToRefs } from "pinia";
5-
import { MessageStatus } from "@/resources/Message";
5+
import Message, { MessageStatus } from "@/resources/Message";
66
import moment from "moment";
77
import { useRoute } from "vue-router";
88
import FilterInput from "@/components/FilterInput.vue";
99
import ResultsCount from "@/components/ResultsCount.vue";
1010
import DropDown from "@/components/DropDown.vue";
11-
import { computed } from "vue";
12-
import { formatDotNetTimespan, formatTypeName } from "@/composables/formatUtils.ts";
11+
import { computed, ref, watch } from "vue";
12+
import { dotNetTimespanToMilliseconds, formatDotNetTimespan, formatTypeName } from "@/composables/formatUtils.ts";
13+
import EndpointSelector from "@/components/audit/EndpointSelector.vue";
1314
1415
const store = useAuditStore();
1516
const { messages, sortBy, totalCount, messageFilterString, selectedEndpointName, endpoints, itemsPerPage } = storeToRefs(store);
1617
const route = useRoute();
1718
1819
const endpointNames = computed(() => {
19-
return endpoints.value.map((endpoint) => ({
20-
text: endpoint.name,
21-
value: endpoint.name,
22-
}));
20+
return [...new Set(endpoints.value.map((endpoint) => endpoint.name))];
2321
});
24-
const selectedEndpointItem = computed(() => ({ text: selectedEndpointName.value, value: selectedEndpointName.value }));
22+
2523
const sortByItems = [
2624
{ text: "Latest sent", value: "time_sent,desc" },
2725
{ text: "Oldest sent", value: "time_sent,asc" },
2826
{ text: "Fastest processing", value: "processing_time,asc" },
2927
{ text: "Slowest processing", value: "processing_time,desc" },
3028
];
29+
const numberOfItemsPerPage = ["50", "100", "250", "500"];
3130
const selectedSortByItem = computed(() => sortByItems.find((item) => item.value === `${sortBy.value.property},${sortBy.value.isAscending ? "asc" : "desc"}`));
31+
const selectedItemsPerPage = ref(itemsPerPage.value.toString());
32+
33+
watch(selectedItemsPerPage, (newValue) => {
34+
itemsPerPage.value = Number(newValue);
35+
});
3236
3337
function setSortBy(item: { text: string; value: string }) {
3438
const strings = item.value.split(",");
3539
sortBy.value = { isAscending: strings[1] === "asc", property: strings[0] };
3640
}
41+
3742
function statusToName(messageStatus: MessageStatus) {
3843
switch (messageStatus) {
3944
case MessageStatus.Successful:
@@ -67,6 +72,26 @@ function statusToIcon(messageStatus: MessageStatus) {
6772
return "fa retry-issued";
6873
}
6974
}
75+
76+
function hasWarning(message: Message) {
77+
if (message.status === MessageStatus.ResolvedSuccessfully) {
78+
return true;
79+
}
80+
81+
if (dotNetTimespanToMilliseconds(message.critical_time) < 0) {
82+
return true;
83+
}
84+
85+
if (dotNetTimespanToMilliseconds(message.processing_time) < 0) {
86+
return true;
87+
}
88+
89+
if (dotNetTimespanToMilliseconds(message.delivery_time) < 0) {
90+
return true;
91+
}
92+
93+
return false;
94+
}
7095
</script>
7196

7297
<template>
@@ -77,33 +102,10 @@ function statusToIcon(messageStatus: MessageStatus) {
77102
<FilterInput v-model="messageFilterString" placeholder="Search messages..." aria-label="Search messages" />
78103
</div>
79104
<div>
80-
<DropDown
81-
label="Filter by endpoint"
82-
:callback="
83-
(item) => {
84-
selectedEndpointName = item.value;
85-
}
86-
"
87-
:select-item="selectedEndpointItem"
88-
:items="endpointNames"
89-
/>
105+
<EndpointSelector :items="numberOfItemsPerPage" instructions="Select how many result to display" v-model="selectedItemsPerPage" item-name="result" label="Show" default-empty-text="Any" :show-clear="false" :show-filter="false" />
90106
</div>
91107
<div>
92-
<DropDown
93-
label="Show"
94-
:callback="
95-
(item) => {
96-
itemsPerPage = parseInt(item.value, 10);
97-
}
98-
"
99-
:select-item="{ text: itemsPerPage.toString(), value: itemsPerPage.toString() }"
100-
:items="[
101-
{ text: '50', value: '50' },
102-
{ text: '100', value: '100' },
103-
{ text: '250', value: '250' },
104-
{ text: '500', value: '500' },
105-
]"
106-
/>
108+
<EndpointSelector :items="endpointNames" instructions="Select an endpoint" v-model="selectedEndpointName" item-name="endpoint" label="Endpoint" default-empty-text="Any" :show-clear="true" :show-filter="true" />
107109
</div>
108110
<div>
109111
<DropDown label="Sort by" :callback="setSortBy" :select-item="selectedSortByItem" :items="sortByItems" />
@@ -114,13 +116,16 @@ function statusToIcon(messageStatus: MessageStatus) {
114116
<ResultsCount :displayed="messages.length" :total="totalCount" />
115117
</div>
116118
<div class="row results-table">
117-
<section class="section-table" role="table" aria-label="endpoint-instances">
119+
<section role="table" aria-label="endpoint-instances">
118120
<!--Table rows-->
119121
<!--NOTE: currently the DataView pages on the client only: we need to make it server data aware (i.e. the total will be the count from the server, not the length of the data we have locally)-->
120-
<div class="messages" role="rowgroup" aria-label="messages">
122+
<div role="rowgroup" aria-label="messages">
121123
<div role="row" :aria-label="message.message_id" class="row grid-row" v-for="message in messages" :key="message.id">
122124
<div role="cell" aria-label="status" class="status" :title="statusToName(message.status)">
123-
<div class="status-icon" :class="statusToIcon(message.status)"></div>
125+
<div class="status-container">
126+
<div class="status-icon" :class="statusToIcon(message.status)"></div>
127+
<div v-if="hasWarning(message)" class="warning"></div>
128+
</div>
124129
</div>
125130
<div role="cell" aria-label="message-id" class="col-3 message-id">
126131
<div class="box-header">
@@ -169,78 +174,58 @@ function statusToIcon(messageStatus: MessageStatus) {
169174
gap: 1.1rem;
170175
}
171176
172-
.section-table {
173-
overflow: auto;
174-
flex: 1;
175-
display: flex;
176-
flex-direction: column;
177-
}
178-
179-
.messages {
180-
flex: 1;
181-
overflow: auto;
182-
}
183-
184177
.status {
185178
width: 5em;
186179
text-align: center;
187180
}
188181
189-
.status-icon {
182+
.status-container {
190183
color: white;
191-
border-radius: 0.75em;
192-
width: 1.2em;
193-
height: 1.2em;
184+
width: 20px;
185+
height: 20px;
186+
position: relative;
194187
}
195188
196-
.status-icon::before {
197-
vertical-align: middle;
198-
font-size: 0.85em;
189+
.status-icon {
190+
background-position: center;
191+
background-repeat: no-repeat;
192+
height: 20px;
193+
width: 20px;
199194
}
200195
201-
.successful {
202-
background: #6cc63f;
196+
.warning {
197+
background-image: url("@/assets/warning.svg");
198+
background-position: bottom;
199+
background-repeat: no-repeat;
200+
height: 13px;
201+
width: 13px;
202+
position: absolute;
203+
right: 0;
204+
bottom: 0;
203205
}
204-
.successful::before {
205-
content: "\f00c";
206+
207+
.successful {
208+
background-image: url("@/assets/status_successful.svg");
206209
}
207210
208211
.resolved-successfully {
209-
background: #3f881b;
210-
}
211-
.resolved-successfully::before {
212-
content: "\f01e";
212+
background-image: url("@/assets/status_resolved.svg");
213213
}
214214
215215
.failed {
216-
background: #c63f3f;
217-
}
218-
.failed::before {
219-
content: "\f00d";
216+
background-image: url("@/assets/status_failed.svg");
220217
}
221218
222219
.archived {
223-
background: #000000;
224-
}
225-
.archived::before {
226-
content: "\f187";
227-
font-size: 0.85em;
220+
background-image: url("@/assets/status_archived.svg");
228221
}
229222
230223
.repeated-failure {
231-
background: #c63f3f;
232-
}
233-
.repeated-failure::before {
234-
content: "\f00d\f00d";
235-
font-size: 0.6em;
224+
background-image: url("@/assets/status_repeated_failed.svg");
236225
}
237226
238227
.retry-issued {
239-
background: #cccccc;
240-
color: #000000;
241-
}
242-
.retry-issued::before {
243-
content: "\f01e";
228+
background-image: url("@/assets/status_retry_issued.svg");
244229
}
245230
246231
.grid-row {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<script setup lang="ts">
2+
import FilterInput from "@/components/FilterInput.vue";
3+
import { ref, watch } from "vue";
4+
const selected = defineModel<string>({ required: true });
5+
const props = defineProps<{ items: string[]; instructions: string; itemName: string; label: string; defaultEmptyText: string; showClear: boolean; showFilter: boolean }>();
6+
const filter = ref("");
7+
const filteredItems = ref(props.items);
8+
9+
watch([filter, () => props.items], () => {
10+
if (filter.value !== "" && filter.value !== null && filter.value !== undefined) {
11+
filteredItems.value = props.items.filter((item) => item.toLowerCase().includes(filter.value.toLowerCase()));
12+
} else {
13+
filteredItems.value = props.items;
14+
}
15+
});
16+
17+
function onclick(item: string, isSelected: boolean) {
18+
if (isSelected) {
19+
selected.value = "";
20+
} else {
21+
selected.value = item;
22+
}
23+
}
24+
</script>
25+
26+
<template>
27+
<div class="dropdown">
28+
<label v-if="label" class="control-label" style="float: inherit">{{ label }}:</label>
29+
<button type="button" :aria-label="label ?? 'open dropdown menu'" class="btn btn-dropdown dropdown-toggle sp-btn-menu" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
30+
{{ selected ? selected : defaultEmptyText }}
31+
</button>
32+
<div class="dropdown-menu wrapper">
33+
<div class="instructions">{{ instructions }}</div>
34+
<div v-if="showFilter" class="filter-input">
35+
<FilterInput v-model="filter" :placeholder="`Filter ${itemName}s`" />
36+
</div>
37+
<div class="items-container">
38+
<div class="item-container" v-if="showClear && selected">
39+
<svg class="fa-cross" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
40+
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
41+
<path
42+
d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"
43+
/>
44+
</svg>
45+
<a class="clear" @click.prevent="() => onclick('', true)"> Clear selected {{ itemName }}</a>
46+
</div>
47+
<div class="item-container" v-for="item in filteredItems" :key="item">
48+
<i v-if="item === selected" class="fa fa-check" />
49+
<a class="item" :class="{ selected: item === selected }" @click.prevent="() => onclick(item, item === selected)">{{ item }}</a>
50+
</div>
51+
</div>
52+
</div>
53+
</div>
54+
</template>
55+
56+
<style scoped>
57+
.wrapper {
58+
padding: 0.5em;
59+
min-width: 200px;
60+
}
61+
.instructions {
62+
font-weight: bold;
63+
margin-bottom: 0.5em;
64+
}
65+
.items-container {
66+
max-height: 300px;
67+
overflow-y: auto;
68+
}
69+
.item {
70+
margin-left: 20px;
71+
font-size: 14px;
72+
font-weight: 400;
73+
cursor: pointer;
74+
color: #262626;
75+
text-decoration: none;
76+
}
77+
.item-container {
78+
padding: 0.3em 0;
79+
}
80+
81+
.item-container:hover {
82+
background-color: #f5f5f5;
83+
}
84+
85+
.filter-input {
86+
margin-bottom: 0.5em;
87+
}
88+
89+
.clear {
90+
color: #262626;
91+
font-size: 14px;
92+
font-weight: 400;
93+
cursor: pointer;
94+
text-decoration: none;
95+
}
96+
97+
.selected {
98+
margin-left: 6px;
99+
}
100+
101+
.fa-cross {
102+
width: 16px;
103+
height: 16px;
104+
}
105+
</style>

0 commit comments

Comments
 (0)