Skip to content

Commit 7b5d933

Browse files
committed
Adds filter functionality to the event history page
Implements date range, service name, region, and event type filters to enhance user experience in the event history section. Validates date inputs to prevent logical errors, ensuring the start date is not after the end date and the end date is not in the future. Provides a clear and user-friendly interface with options to reset filters. Improves the event listing by filtering and ordering events based on user-selected criteria.
1 parent b699fbb commit 7b5d933

1 file changed

Lines changed: 195 additions & 3 deletions

File tree

src/Pages/History.tsx

Lines changed: 195 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,116 @@
1+
import { ScaleButton, ScaleDropdownSelect, ScaleDropdownSelectItem, ScaleTextField } from "@telekom/scale-components-react";
2+
import dayjs from "dayjs";
13
import { chain } from "lodash";
4+
import { useState } from "react";
25
import { Helmet } from "react-helmet";
6+
import { EventType } from "~/Components/Event/Enums";
37
import { EventItem } from "~/Components/History/EventItem";
48
import { useStatus } from "~/Services/Status";
59

610
/**
711
* @author Aloento
812
* @since 1.0.0
9-
* @version 0.1.0
13+
* @version 1.0.0
1014
*/
1115
export function History() {
1216
const { DB } = useStatus();
1317

18+
const [filters, setFilters] = useState({
19+
startDate: dayjs().add(-6, 'month').format('YYYY-MM-DD'),
20+
endDate: "",
21+
serviceName: "",
22+
region: "",
23+
eventType: "",
24+
});
25+
26+
const [validation, setValidation] = useState({
27+
startDate: "",
28+
endDate: "",
29+
});
30+
31+
function validateDates(startDate: string, endDate: string) {
32+
const errors = { startDate: "", endDate: "" };
33+
34+
if (startDate && endDate) {
35+
const start = dayjs(startDate);
36+
const end = dayjs(endDate);
37+
38+
if (start.isAfter(end)) {
39+
errors.startDate = "Start date cannot be later than end date.";
40+
}
41+
}
42+
43+
if (endDate) {
44+
const end = dayjs(endDate);
45+
const now = dayjs();
46+
47+
if (end.isAfter(now)) {
48+
errors.endDate = "End date cannot be in the future.";
49+
}
50+
}
51+
52+
setValidation(errors);
53+
return !errors.startDate && !errors.endDate;
54+
}
55+
56+
function clearFilters() {
57+
setFilters({
58+
startDate: dayjs().add(-6, 'month').format('YYYY-MM-DD'),
59+
endDate: "",
60+
serviceName: "",
61+
region: "",
62+
eventType: "",
63+
});
64+
setValidation({
65+
startDate: "",
66+
endDate: "",
67+
});
68+
}
69+
70+
const filteredEvents = chain(DB.Events)
71+
.filter(event => {
72+
if (filters.startDate) {
73+
const filterStart = dayjs(filters.startDate).startOf('day');
74+
const eventStart = dayjs(event.Start);
75+
if (eventStart.isBefore(filterStart)) return false;
76+
}
77+
78+
if (filters.endDate) {
79+
const filterEnd = dayjs(filters.endDate).endOf('day');
80+
const eventStart = dayjs(event.Start);
81+
const eventEnd = event.End ? dayjs(event.End) : eventStart;
82+
if (eventEnd.isAfter(filterEnd)) return false;
83+
}
84+
85+
if (filters.serviceName) {
86+
const serviceNames = Array.from(event.RegionServices)
87+
.map(rs => ({
88+
name: rs.Service.Name.toLowerCase(),
89+
abbr: rs.Service.Abbr.toLowerCase()
90+
}));
91+
const searchTerm = filters.serviceName.toLowerCase();
92+
const hasService = serviceNames.some(service =>
93+
service.name.includes(searchTerm) || service.abbr.includes(searchTerm)
94+
);
95+
if (!hasService) return false;
96+
}
97+
98+
if (filters.region) {
99+
const regionNames = Array.from(event.RegionServices)
100+
.map(rs => rs.Region.Name);
101+
const hasRegion = regionNames.includes(filters.region);
102+
if (!hasRegion) return false;
103+
}
104+
105+
if (filters.eventType && event.Type !== filters.eventType) {
106+
return false;
107+
}
108+
109+
return true;
110+
})
111+
.orderBy(x => x.Start, "desc")
112+
.value();
113+
14114
return <>
15115
<Helmet>
16116
<title>Timeline - OTC Status Dashboard</title>
@@ -22,9 +122,101 @@ export function History() {
22122
</h3>
23123
</section>
24124

125+
<section className="flex flex-col rounded-lg bg-white shadow-md p-5 gap-y-1.5">
126+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4">
127+
<ScaleTextField
128+
type="date"
129+
label="Start Date"
130+
placeholder="Select start date"
131+
value={filters.startDate}
132+
invalid={!!validation.startDate}
133+
helperText={validation.startDate}
134+
onScale-input={(e) => {
135+
const newStartDate = e.target.value as string;
136+
setFilters(prev => ({
137+
...prev,
138+
startDate: newStartDate
139+
}));
140+
validateDates(newStartDate, filters.endDate);
141+
}}
142+
/>
143+
144+
<ScaleTextField
145+
type="date"
146+
label="End Date"
147+
placeholder="Select end date"
148+
value={filters.endDate}
149+
invalid={!!validation.endDate}
150+
helperText={validation.endDate}
151+
onScale-input={(e) => {
152+
const newEndDate = e.target.value as string;
153+
setFilters(prev => ({
154+
...prev,
155+
endDate: newEndDate
156+
}));
157+
validateDates(filters.startDate, newEndDate);
158+
}}
159+
/>
160+
161+
<ScaleTextField
162+
label="Service Name"
163+
placeholder="Search service name"
164+
value={filters.serviceName}
165+
onScale-input={(e) => setFilters(prev => ({
166+
...prev,
167+
serviceName: e.target.value as string
168+
}))}
169+
/>
170+
171+
<ScaleDropdownSelect
172+
label="Region"
173+
value={filters.region}
174+
onScale-change={(e) => setFilters(prev => ({
175+
...prev,
176+
region: e.target.value as string
177+
}))}
178+
>
179+
<ScaleDropdownSelectItem value="">All Regions</ScaleDropdownSelectItem>
180+
{DB.Regions.map((region, i) => (
181+
<ScaleDropdownSelectItem value={region.Name} key={i}>
182+
{region.Name}
183+
</ScaleDropdownSelectItem>
184+
))}
185+
</ScaleDropdownSelect>
186+
187+
<ScaleDropdownSelect
188+
label="Event Type"
189+
value={filters.eventType}
190+
onScale-change={(e) => setFilters(prev => ({
191+
...prev,
192+
eventType: e.target.value as string
193+
}))}
194+
>
195+
<ScaleDropdownSelectItem value="">All Types</ScaleDropdownSelectItem>
196+
{Object.values(EventType).slice(1).map((type, i) => (
197+
<ScaleDropdownSelectItem value={type} key={i}>
198+
{type}
199+
</ScaleDropdownSelectItem>
200+
))}
201+
</ScaleDropdownSelect>
202+
</div>
203+
204+
<div className="flex justify-between items-center mt-4">
205+
<span className="text-sm text-gray-600">
206+
Found {filteredEvents.length} events, {DB.Events.length - filteredEvents.length} filtered out
207+
</span>
208+
<ScaleButton
209+
variant="secondary"
210+
size="small"
211+
onClick={clearFilters}
212+
>
213+
Clear Filters
214+
</ScaleButton>
215+
</div>
216+
</section>
217+
25218
<ol className="flex flex-col pl-3 xl:pl-0">
26-
{chain(DB.Events)
27-
.orderBy(x => x.Start, "desc")
219+
{chain(filteredEvents)
28220
.map((event, index, events) => [events[index - 1], event])
29221
.map(([prev, curr]) => (
30222
<EventItem key={curr.Id} Prev={prev} Curr={curr} />

0 commit comments

Comments
 (0)