Skip to content

Commit 969b4eb

Browse files
committed
Create filter
1 parent 30e5ac3 commit 969b4eb

File tree

6 files changed

+329
-102
lines changed

6 files changed

+329
-102
lines changed

frontend/src/pages/Reports/ReportsListPage.scss

Lines changed: 54 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
&__header {
55
box-shadow: none;
6+
--background: var(--ion-background-color);
67

78
ion-toolbar {
89
--border-width: 0;
@@ -19,150 +20,110 @@
1920
}
2021

2122
&__title-icon {
22-
margin-right: 12px;
23-
color: #333;
24-
padding: 8px;
25-
background-color: #f0f2f5;
26-
border-radius: 50%;
27-
display: flex;
28-
align-items: center;
29-
justify-content: center;
30-
width: 18px;
31-
height: 18px;
23+
font-size: 1.5rem;
24+
margin-right: 0.5rem;
25+
color: var(--ion-color-primary);
3226
}
3327

3428
&__title {
35-
font-size: 18px;
29+
font-size: 1.25rem;
3630
font-weight: 600;
3731
margin: 0;
38-
color: #333;
3932
}
4033

4134
&__actions {
4235
display: flex;
4336
align-items: center;
44-
justify-content: flex-end;
4537
}
4638

4739
&__sort-button,
4840
&__filter-button {
49-
--padding-start: 0;
50-
--padding-end: 0;
41+
--padding-start: 0.5rem;
42+
--padding-end: 0.5rem;
43+
margin: 0;
5144
height: 36px;
52-
width: 36px;
53-
--background: transparent;
54-
--color: #4355b9;
55-
--box-shadow: none;
56-
--ripple-color: transparent;
57-
margin-left: 10px;
58-
59-
.custom-icon-wrapper {
60-
width: 36px;
61-
height: 36px;
62-
display: flex;
63-
align-items: center;
64-
justify-content: center;
65-
border-radius: 50%;
66-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
67-
border: 1px solid rgba(0, 0, 0, 0.03);
68-
}
45+
}
6946

70-
.custom-icon {
71-
width: 22px;
72-
height: 22px;
73-
}
47+
.custom-icon-wrapper {
48+
display: flex;
49+
align-items: center;
50+
justify-content: center;
7451
}
7552

76-
&__content-container {
77-
--padding-top: 0;
53+
.custom-icon {
54+
width: 20px;
55+
height: 20px;
7856
}
7957

8058
&__filter {
81-
padding: 8px 16px;
59+
margin-bottom: 1rem;
8260
}
8361

8462
&__segment-wrapper {
85-
ion-segment {
86-
--background: #ebeef9;
87-
border-radius: 50px;
88-
height: 40px;
89-
overflow: hidden;
90-
--border-radius: 50px;
91-
92-
ion-segment-button {
93-
--color: #777;
94-
--color-checked: #000;
95-
--indicator-color: white;
96-
--background-checked: white;
97-
--border-radius: 50px;
98-
--border-color: transparent;
99-
text-transform: none;
100-
font-weight: 500;
101-
letter-spacing: normal;
102-
font-size: 14px;
103-
min-height: 36px;
104-
105-
&::part(indicator) {
106-
border-radius: 50px;
107-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
108-
}
109-
}
110-
}
63+
padding: 0 1rem;
64+
}
65+
66+
&__category-tags {
67+
display: flex;
68+
flex-wrap: wrap;
69+
padding: 0.5rem 1rem;
70+
margin-bottom: 0.5rem;
71+
}
72+
73+
&__content-container {
74+
--padding-top: 0;
75+
--background: var(--ion-background-color);
11176
}
11277

11378
&__content {
114-
padding: 0;
79+
padding-bottom: 1rem;
11580
}
11681

11782
&__list {
118-
background-color: transparent;
119-
padding: 12px;
120-
margin: 0;
121-
122-
ion-item {
123-
--padding-start: 0;
124-
--inner-padding-end: 0;
125-
--background: transparent;
126-
}
83+
padding: 0;
84+
background: transparent;
12785
}
12886

12987
&__empty-state {
130-
padding: 2rem;
88+
height: 100%;
13189
display: flex;
13290
flex-direction: column;
13391
align-items: center;
13492
justify-content: center;
93+
padding: 2rem;
13594
text-align: center;
13695
}
13796

138-
&__no-bookmarks {
139-
display: flex;
140-
flex-direction: column;
141-
align-items: center;
142-
justify-content: center;
97+
&__no-bookmarks,
98+
&__no-matches {
14399
text-align: center;
144-
padding: 2rem 1rem;
100+
padding: 2rem;
145101

146102
h3 {
147103
font-size: 1.2rem;
148-
margin-bottom: 0.5rem;
149-
color: #333;
150104
font-weight: 600;
105+
margin-bottom: 0.5rem;
151106
}
152107

153108
p {
154-
font-size: 0.9rem;
155-
color: #666;
156-
margin: 0;
109+
color: var(--ion-color-medium);
110+
margin-bottom: 1.5rem;
157111
}
158112
}
159113

160-
// Skeleton loading styles
114+
&__filter-modal {
115+
--height: 80%;
116+
--width: 100%;
117+
--border-radius: 1rem 1rem 0 0;
118+
--box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1);
119+
}
120+
161121
.skeleton {
162-
width: 48px;
163-
height: 48px;
164-
border-radius: 50%;
165-
background-color: #ebeef9;
166-
margin-right: 16px;
122+
background-color: rgba(var(--ion-color-medium-rgb), 0.2);
123+
border-radius: 4px;
124+
height: 40px;
125+
width: 40px;
126+
margin-right: 1rem;
167127
}
168128
}
129+

frontend/src/pages/Reports/ReportsListPage.tsx

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
IonIcon,
1313
IonButton,
1414
IonToast,
15+
IonModal,
1516
} from '@ionic/react';
1617
import { useTranslation } from 'react-i18next';
1718
import { useHistory } from 'react-router-dom';
@@ -20,11 +21,13 @@ import { fetchAllReports, toggleReportBookmark } from 'common/api/reportService'
2021
import { useMarkReportAsRead } from 'common/hooks/useReports';
2122
import ReportItem from 'pages/Home/components/ReportItem/ReportItem';
2223
import NoReportsMessage from 'pages/Home/components/NoReportsMessage/NoReportsMessage';
23-
import { useState, useMemo, useEffect } from 'react';
24+
import { useState, useMemo, useEffect, useRef } from 'react';
2425
import { MedicalReport } from 'common/models/medicalReport';
2526
import { documentTextOutline } from 'ionicons/icons';
2627
import sortSvg from 'assets/icons/sort.svg';
2728
import filterOutlineIcon from 'assets/icons/filter-outline.svg';
29+
import FilterPanel, { CategoryOption } from './components/FilterPanel/FilterPanel';
30+
import CategoryTag from './components/CategoryTag/CategoryTag';
2831

2932
import './ReportsListPage.scss';
3033

@@ -41,7 +44,18 @@ const ReportsListPage: React.FC = () => {
4144
const [filter, setFilter] = useState<FilterOption>('all');
4245
const [sortDirection, setSortDirection] = useState<SortDirection>('desc'); // Default sort by newest first
4346
const [showToast, setShowToast] = useState(false);
44-
const [toastMessage, setToastMessage] = useState('');
47+
const [toastMessage] = useState('');
48+
const [showFilterModal, setShowFilterModal] = useState(false);
49+
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
50+
const filterModalRef = useRef<HTMLIonModalElement>(null);
51+
52+
// Define available categories
53+
const categories: CategoryOption[] = [
54+
{ id: 'general', label: t('category.general', { ns: 'report' }) },
55+
{ id: 'heart', label: t('category.heart', { ns: 'report' }) },
56+
{ id: 'brain', label: t('category.brain', { ns: 'report' }) },
57+
// Add more categories as needed
58+
];
4559

4660
const { data: reports = [], isLoading, isError } = useQuery({
4761
queryKey: ['reports'],
@@ -50,18 +64,25 @@ const ReportsListPage: React.FC = () => {
5064

5165
const { mutate: markAsRead } = useMarkReportAsRead();
5266

53-
// Filter and sort reports based on selected filter and sort direction
67+
// Filter and sort reports based on selected filter, categories, and sort direction
5468
const filteredReports = useMemo(() => {
55-
// First, filter the reports
56-
const filtered = filter === 'all' ? reports : reports.filter(report => report.bookmarked);
69+
// First, filter the reports by bookmark status
70+
let filtered = filter === 'all' ? reports : reports.filter(report => report.bookmarked);
71+
72+
// Then, filter by selected categories if any are selected
73+
if (selectedCategories.length > 0) {
74+
filtered = filtered.filter(report =>
75+
selectedCategories.includes(report.category.toLowerCase())
76+
);
77+
}
5778

58-
// Then, sort the filtered reports by date
79+
// Finally, sort the filtered reports by date
5980
return [...filtered].sort((a, b) => {
6081
const dateA = new Date(a.createdAt).getTime();
6182
const dateB = new Date(b.createdAt).getTime();
6283
return sortDirection === 'desc' ? dateB - dateA : dateA - dateB;
6384
});
64-
}, [reports, filter, sortDirection]);
85+
}, [reports, filter, sortDirection, selectedCategories]);
6586

6687
// Check if there are any bookmarked reports
6788
const hasBookmarkedReports = useMemo(() => {
@@ -118,8 +139,44 @@ const ReportsListPage: React.FC = () => {
118139
};
119140

120141
const handleFilterClick = () => {
121-
setToastMessage(t('list.filterButton', { ns: 'report' }));
122-
setShowToast(true);
142+
setShowFilterModal(true);
143+
};
144+
145+
const handleCloseFilterModal = () => {
146+
filterModalRef.current?.dismiss();
147+
};
148+
149+
const handleApplyFilters = (categories: string[]) => {
150+
setSelectedCategories(categories);
151+
};
152+
153+
const handleRemoveCategory = (categoryId: string) => {
154+
setSelectedCategories(prev => prev.filter(id => id !== categoryId));
155+
};
156+
157+
const handleClearAllCategories = () => {
158+
setSelectedCategories([]);
159+
};
160+
161+
const getCategoryLabel = (categoryId: string): string => {
162+
const category = categories.find(cat => cat.id === categoryId);
163+
return category ? category.label : categoryId;
164+
};
165+
166+
const renderCategoryTags = () => {
167+
if (selectedCategories.length === 0) return null;
168+
169+
return (
170+
<div className="reports-list-page__category-tags">
171+
{selectedCategories.map(categoryId => (
172+
<CategoryTag
173+
key={categoryId}
174+
label={getCategoryLabel(categoryId)}
175+
onRemove={() => handleRemoveCategory(categoryId)}
176+
/>
177+
))}
178+
</div>
179+
);
123180
};
124181

125182
const renderReportsList = () => {
@@ -157,6 +214,14 @@ const ReportsListPage: React.FC = () => {
157214
<h3>{t('list.noBookmarksTitle', { ns: 'report' })}</h3>
158215
<p>{t('list.noBookmarksMessage', { ns: 'report' })}</p>
159216
</div>
217+
) : selectedCategories.length > 0 ? (
218+
<div className="reports-list-page__no-matches">
219+
<h3>{t('list.noMatchesTitle', { ns: 'report' })}</h3>
220+
<p>{t('list.noMatchesMessage', { ns: 'report' })}</p>
221+
<IonButton onClick={handleClearAllCategories}>
222+
{t('list.clearFilters', { ns: 'report' })}
223+
</IonButton>
224+
</div>
160225
) : (
161226
<NoReportsMessage
162227
onUpload={handleUpload}
@@ -235,13 +300,32 @@ const ReportsListPage: React.FC = () => {
235300
</div>
236301
</div>
237302
)}
303+
304+
{/* Display selected category tags */}
305+
{renderCategoryTags()}
306+
238307
<div className="reports-list-page__content">
239308
<IonList className="reports-list-page__list" lines="none">
240309
{renderReportsList()}
241310
</IonList>
242311
</div>
243312
</IonContent>
244313

314+
{/* Filter Modal */}
315+
<IonModal
316+
ref={filterModalRef}
317+
isOpen={showFilterModal}
318+
onDidDismiss={() => setShowFilterModal(false)}
319+
className="reports-list-page__filter-modal"
320+
>
321+
<FilterPanel
322+
categories={categories}
323+
selectedCategories={selectedCategories}
324+
onApply={handleApplyFilters}
325+
onClose={handleCloseFilterModal}
326+
/>
327+
</IonModal>
328+
245329
<IonToast
246330
isOpen={showToast}
247331
onDidDismiss={() => setShowToast(false)}

0 commit comments

Comments
 (0)