From 30e5ac3d5334c8e6b24ff30430d15f1d58a7f20f Mon Sep 17 00:00:00 2001 From: Guido Percu Date: Tue, 22 Apr 2025 14:49:22 -0400 Subject: [PATCH 1/7] Create sort button --- .../src/pages/Reports/ReportsListPage.tsx | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/Reports/ReportsListPage.tsx b/frontend/src/pages/Reports/ReportsListPage.tsx index f1afd065..83d54690 100644 --- a/frontend/src/pages/Reports/ReportsListPage.tsx +++ b/frontend/src/pages/Reports/ReportsListPage.tsx @@ -29,6 +29,7 @@ import filterOutlineIcon from 'assets/icons/filter-outline.svg'; import './ReportsListPage.scss'; type FilterOption = 'all' | 'bookmarked'; +type SortDirection = 'desc' | 'asc'; /** * Page component for displaying a list of all medical reports. @@ -38,6 +39,7 @@ const ReportsListPage: React.FC = () => { const history = useHistory(); const queryClient = useQueryClient(); const [filter, setFilter] = useState('all'); + const [sortDirection, setSortDirection] = useState('desc'); // Default sort by newest first const [showToast, setShowToast] = useState(false); const [toastMessage, setToastMessage] = useState(''); @@ -48,11 +50,18 @@ const ReportsListPage: React.FC = () => { const { mutate: markAsRead } = useMarkReportAsRead(); - // Filter reports based on selected filter + // Filter and sort reports based on selected filter and sort direction const filteredReports = useMemo(() => { - if (filter === 'all') return reports; - return reports.filter(report => report.bookmarked); - }, [reports, filter]); + // First, filter the reports + const filtered = filter === 'all' ? reports : reports.filter(report => report.bookmarked); + + // Then, sort the filtered reports by date + return [...filtered].sort((a, b) => { + const dateA = new Date(a.createdAt).getTime(); + const dateB = new Date(b.createdAt).getTime(); + return sortDirection === 'desc' ? dateB - dateA : dateA - dateB; + }); + }, [reports, filter, sortDirection]); // Check if there are any bookmarked reports const hasBookmarkedReports = useMemo(() => { @@ -104,8 +113,8 @@ const ReportsListPage: React.FC = () => { }; const handleSortClick = () => { - setToastMessage(t('list.sortButton', { ns: 'report' })); - setShowToast(true); + const newSortDirection = sortDirection === 'desc' ? 'asc' : 'desc'; + setSortDirection(newSortDirection); }; const handleFilterClick = () => { From 969b4ebe2e17a855394142bdca2784be811d18ed Mon Sep 17 00:00:00 2001 From: Guido Percu Date: Tue, 22 Apr 2025 15:03:20 -0400 Subject: [PATCH 2/7] Create filter --- .../src/pages/Reports/ReportsListPage.scss | 147 +++++++----------- .../src/pages/Reports/ReportsListPage.tsx | 102 ++++++++++-- .../components/CategoryTag/CategoryTag.scss | 31 ++++ .../components/CategoryTag/CategoryTag.tsx | 26 ++++ .../components/FilterPanel/FilterPanel.scss | 50 ++++++ .../components/FilterPanel/FilterPanel.tsx | 75 +++++++++ 6 files changed, 329 insertions(+), 102 deletions(-) create mode 100644 frontend/src/pages/Reports/components/CategoryTag/CategoryTag.scss create mode 100644 frontend/src/pages/Reports/components/CategoryTag/CategoryTag.tsx create mode 100644 frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss create mode 100644 frontend/src/pages/Reports/components/FilterPanel/FilterPanel.tsx diff --git a/frontend/src/pages/Reports/ReportsListPage.scss b/frontend/src/pages/Reports/ReportsListPage.scss index d0e3fea3..3dfe1af8 100644 --- a/frontend/src/pages/Reports/ReportsListPage.scss +++ b/frontend/src/pages/Reports/ReportsListPage.scss @@ -3,6 +3,7 @@ &__header { box-shadow: none; + --background: var(--ion-background-color); ion-toolbar { --border-width: 0; @@ -19,150 +20,110 @@ } &__title-icon { - margin-right: 12px; - color: #333; - padding: 8px; - background-color: #f0f2f5; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; + font-size: 1.5rem; + margin-right: 0.5rem; + color: var(--ion-color-primary); } &__title { - font-size: 18px; + font-size: 1.25rem; font-weight: 600; margin: 0; - color: #333; } &__actions { display: flex; align-items: center; - justify-content: flex-end; } &__sort-button, &__filter-button { - --padding-start: 0; - --padding-end: 0; + --padding-start: 0.5rem; + --padding-end: 0.5rem; + margin: 0; height: 36px; - width: 36px; - --background: transparent; - --color: #4355b9; - --box-shadow: none; - --ripple-color: transparent; - margin-left: 10px; - - .custom-icon-wrapper { - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); - border: 1px solid rgba(0, 0, 0, 0.03); - } + } - .custom-icon { - width: 22px; - height: 22px; - } + .custom-icon-wrapper { + display: flex; + align-items: center; + justify-content: center; } - &__content-container { - --padding-top: 0; + .custom-icon { + width: 20px; + height: 20px; } &__filter { - padding: 8px 16px; + margin-bottom: 1rem; } &__segment-wrapper { - ion-segment { - --background: #ebeef9; - border-radius: 50px; - height: 40px; - overflow: hidden; - --border-radius: 50px; - - ion-segment-button { - --color: #777; - --color-checked: #000; - --indicator-color: white; - --background-checked: white; - --border-radius: 50px; - --border-color: transparent; - text-transform: none; - font-weight: 500; - letter-spacing: normal; - font-size: 14px; - min-height: 36px; - - &::part(indicator) { - border-radius: 50px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - } - } + padding: 0 1rem; + } + + &__category-tags { + display: flex; + flex-wrap: wrap; + padding: 0.5rem 1rem; + margin-bottom: 0.5rem; + } + + &__content-container { + --padding-top: 0; + --background: var(--ion-background-color); } &__content { - padding: 0; + padding-bottom: 1rem; } &__list { - background-color: transparent; - padding: 12px; - margin: 0; - - ion-item { - --padding-start: 0; - --inner-padding-end: 0; - --background: transparent; - } + padding: 0; + background: transparent; } &__empty-state { - padding: 2rem; + height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; + padding: 2rem; text-align: center; } - &__no-bookmarks { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; + &__no-bookmarks, + &__no-matches { text-align: center; - padding: 2rem 1rem; + padding: 2rem; h3 { font-size: 1.2rem; - margin-bottom: 0.5rem; - color: #333; font-weight: 600; + margin-bottom: 0.5rem; } p { - font-size: 0.9rem; - color: #666; - margin: 0; + color: var(--ion-color-medium); + margin-bottom: 1.5rem; } } - // Skeleton loading styles + &__filter-modal { + --height: 80%; + --width: 100%; + --border-radius: 1rem 1rem 0 0; + --box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1); + } + .skeleton { - width: 48px; - height: 48px; - border-radius: 50%; - background-color: #ebeef9; - margin-right: 16px; + background-color: rgba(var(--ion-color-medium-rgb), 0.2); + border-radius: 4px; + height: 40px; + width: 40px; + margin-right: 1rem; } } + diff --git a/frontend/src/pages/Reports/ReportsListPage.tsx b/frontend/src/pages/Reports/ReportsListPage.tsx index 83d54690..de3c2168 100644 --- a/frontend/src/pages/Reports/ReportsListPage.tsx +++ b/frontend/src/pages/Reports/ReportsListPage.tsx @@ -12,6 +12,7 @@ import { IonIcon, IonButton, IonToast, + IonModal, } from '@ionic/react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; @@ -20,11 +21,13 @@ import { fetchAllReports, toggleReportBookmark } from 'common/api/reportService' import { useMarkReportAsRead } from 'common/hooks/useReports'; import ReportItem from 'pages/Home/components/ReportItem/ReportItem'; import NoReportsMessage from 'pages/Home/components/NoReportsMessage/NoReportsMessage'; -import { useState, useMemo, useEffect } from 'react'; +import { useState, useMemo, useEffect, useRef } from 'react'; import { MedicalReport } from 'common/models/medicalReport'; import { documentTextOutline } from 'ionicons/icons'; import sortSvg from 'assets/icons/sort.svg'; import filterOutlineIcon from 'assets/icons/filter-outline.svg'; +import FilterPanel, { CategoryOption } from './components/FilterPanel/FilterPanel'; +import CategoryTag from './components/CategoryTag/CategoryTag'; import './ReportsListPage.scss'; @@ -41,7 +44,18 @@ const ReportsListPage: React.FC = () => { const [filter, setFilter] = useState('all'); const [sortDirection, setSortDirection] = useState('desc'); // Default sort by newest first const [showToast, setShowToast] = useState(false); - const [toastMessage, setToastMessage] = useState(''); + const [toastMessage] = useState(''); + const [showFilterModal, setShowFilterModal] = useState(false); + const [selectedCategories, setSelectedCategories] = useState([]); + const filterModalRef = useRef(null); + + // Define available categories + const categories: CategoryOption[] = [ + { id: 'general', label: t('category.general', { ns: 'report' }) }, + { id: 'heart', label: t('category.heart', { ns: 'report' }) }, + { id: 'brain', label: t('category.brain', { ns: 'report' }) }, + // Add more categories as needed + ]; const { data: reports = [], isLoading, isError } = useQuery({ queryKey: ['reports'], @@ -50,18 +64,25 @@ const ReportsListPage: React.FC = () => { const { mutate: markAsRead } = useMarkReportAsRead(); - // Filter and sort reports based on selected filter and sort direction + // Filter and sort reports based on selected filter, categories, and sort direction const filteredReports = useMemo(() => { - // First, filter the reports - const filtered = filter === 'all' ? reports : reports.filter(report => report.bookmarked); + // First, filter the reports by bookmark status + let filtered = filter === 'all' ? reports : reports.filter(report => report.bookmarked); + + // Then, filter by selected categories if any are selected + if (selectedCategories.length > 0) { + filtered = filtered.filter(report => + selectedCategories.includes(report.category.toLowerCase()) + ); + } - // Then, sort the filtered reports by date + // Finally, sort the filtered reports by date return [...filtered].sort((a, b) => { const dateA = new Date(a.createdAt).getTime(); const dateB = new Date(b.createdAt).getTime(); return sortDirection === 'desc' ? dateB - dateA : dateA - dateB; }); - }, [reports, filter, sortDirection]); + }, [reports, filter, sortDirection, selectedCategories]); // Check if there are any bookmarked reports const hasBookmarkedReports = useMemo(() => { @@ -118,8 +139,44 @@ const ReportsListPage: React.FC = () => { }; const handleFilterClick = () => { - setToastMessage(t('list.filterButton', { ns: 'report' })); - setShowToast(true); + setShowFilterModal(true); + }; + + const handleCloseFilterModal = () => { + filterModalRef.current?.dismiss(); + }; + + const handleApplyFilters = (categories: string[]) => { + setSelectedCategories(categories); + }; + + const handleRemoveCategory = (categoryId: string) => { + setSelectedCategories(prev => prev.filter(id => id !== categoryId)); + }; + + const handleClearAllCategories = () => { + setSelectedCategories([]); + }; + + const getCategoryLabel = (categoryId: string): string => { + const category = categories.find(cat => cat.id === categoryId); + return category ? category.label : categoryId; + }; + + const renderCategoryTags = () => { + if (selectedCategories.length === 0) return null; + + return ( +
+ {selectedCategories.map(categoryId => ( + handleRemoveCategory(categoryId)} + /> + ))} +
+ ); }; const renderReportsList = () => { @@ -157,6 +214,14 @@ const ReportsListPage: React.FC = () => {

{t('list.noBookmarksTitle', { ns: 'report' })}

{t('list.noBookmarksMessage', { ns: 'report' })}

+ ) : selectedCategories.length > 0 ? ( +
+

{t('list.noMatchesTitle', { ns: 'report' })}

+

{t('list.noMatchesMessage', { ns: 'report' })}

+ + {t('list.clearFilters', { ns: 'report' })} + +
) : ( { )} + + {/* Display selected category tags */} + {renderCategoryTags()} +
{renderReportsList()} @@ -242,6 +311,21 @@ const ReportsListPage: React.FC = () => {
+ {/* Filter Modal */} + setShowFilterModal(false)} + className="reports-list-page__filter-modal" + > + + + setShowToast(false)} diff --git a/frontend/src/pages/Reports/components/CategoryTag/CategoryTag.scss b/frontend/src/pages/Reports/components/CategoryTag/CategoryTag.scss new file mode 100644 index 00000000..a6a68995 --- /dev/null +++ b/frontend/src/pages/Reports/components/CategoryTag/CategoryTag.scss @@ -0,0 +1,31 @@ +.category-tag { + display: inline-flex; + align-items: center; + background-color: rgba(var(--ion-color-primary-rgb), 0.1); + border-radius: 16px; + padding: 4px 8px 4px 12px; + margin-right: 8px; + margin-bottom: 8px; + + &__label { + font-size: 14px; + color: var(--ion-color-primary); + font-weight: 500; + } + + &__remove-button { + display: flex; + align-items: center; + justify-content: center; + background: none; + border: none; + padding: 0; + margin-left: 4px; + cursor: pointer; + } + + &__remove-icon { + color: var(--ion-color-primary); + font-size: 16px; + } +} diff --git a/frontend/src/pages/Reports/components/CategoryTag/CategoryTag.tsx b/frontend/src/pages/Reports/components/CategoryTag/CategoryTag.tsx new file mode 100644 index 00000000..c08224dd --- /dev/null +++ b/frontend/src/pages/Reports/components/CategoryTag/CategoryTag.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { IonIcon } from '@ionic/react'; +import { close } from 'ionicons/icons'; +import './CategoryTag.scss'; + +interface CategoryTagProps { + label: string; + onRemove: () => void; +} + +const CategoryTag: React.FC = ({ label, onRemove }) => { + return ( +
+ {label} + +
+ ); +}; + +export default CategoryTag; diff --git a/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss b/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss new file mode 100644 index 00000000..b423d1ce --- /dev/null +++ b/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss @@ -0,0 +1,50 @@ +.filter-panel { + padding: 1rem; + display: flex; + flex-direction: column; + height: 100%; + background-color: var(--ion-background-color); + + &__title { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: 1.5rem; + color: var(--ion-text-color); + } + + &__category-section { + flex: 1; + margin-bottom: 1rem; + } + + &__category-title { + font-size: 1.1rem; + font-weight: 500; + margin-bottom: 1rem; + color: var(--ion-text-color); + } + + &__category-list { + background: transparent; + } + + &__category-item { + --padding-start: 0; + --inner-padding-end: 0; + --background: transparent; + margin-bottom: 0.5rem; + } + + &__actions { + padding-bottom: env(safe-area-inset-bottom); + } + + &__apply-button { + --background: var(--ion-color-primary); + --color: white; + height: 44px; + font-weight: 600; + margin: 0; + --border-radius: 8px; + } +} diff --git a/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.tsx b/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.tsx new file mode 100644 index 00000000..d23dfc82 --- /dev/null +++ b/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.tsx @@ -0,0 +1,75 @@ +import React, { useState } from 'react'; +import { IonButton, IonList, IonItem, IonLabel, IonCheckbox } from '@ionic/react'; +import { useTranslation } from 'react-i18next'; +import './FilterPanel.scss'; + +export interface CategoryOption { + id: string; + label: string; +} + +interface FilterPanelProps { + categories: CategoryOption[]; + selectedCategories: string[]; + onApply: (selectedCategories: string[]) => void; + onClose: () => void; +} + +const FilterPanel: React.FC = ({ + categories, + selectedCategories: initialSelectedCategories, + onApply, + onClose, +}) => { + const { t } = useTranslation(['report', 'common']); + const [selectedCategories, setSelectedCategories] = useState(initialSelectedCategories); + + const handleCategoryToggle = (categoryId: string) => { + setSelectedCategories((prev) => { + if (prev.includes(categoryId)) { + return prev.filter((id) => id !== categoryId); + } else { + return [...prev, categoryId]; + } + }); + }; + + const handleApply = () => { + onApply(selectedCategories); + onClose(); + }; + + return ( +
+

{t('filter.title', { ns: 'report' })}

+ +
+

{t('filter.category', { ns: 'report' })}

+ + {categories.map((category) => ( + + {category.label} + handleCategoryToggle(category.id)} + /> + + ))} + +
+ +
+ + {t('filter.apply', { ns: 'report' })} + +
+
+ ); +}; + +export default FilterPanel; From 9411e71adb6048da51b139c4628c94c07193443f Mon Sep 17 00:00:00 2001 From: Guido Percu Date: Tue, 22 Apr 2025 19:01:40 -0400 Subject: [PATCH 3/7] Fix categories translations --- .../common/utils/i18n/resources/en/report.json | 18 ++++++++++++++---- .../common/utils/i18n/resources/es/report.json | 17 ++++++++++++++++- .../common/utils/i18n/resources/fr/report.json | 17 ++++++++++++++++- .../Home/components/ReportItem/ReportItem.tsx | 8 ++++---- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/frontend/src/common/utils/i18n/resources/en/report.json b/frontend/src/common/utils/i18n/resources/en/report.json index d98c022d..0e4cf661 100644 --- a/frontend/src/common/utils/i18n/resources/en/report.json +++ b/frontend/src/common/utils/i18n/resources/en/report.json @@ -49,10 +49,20 @@ "filterBookmarked": "Bookmarked", "noBookmarksTitle": "No Bookmarked Reports", "noBookmarksMessage": "Bookmark reports to find them quickly here", + "noMatchesTitle": "No Matching Reports", + "noMatchesMessage": "No reports match your current filters. Try changing or clearing your filters.", + "clearFilters": "Clear Filters", "sortButton": "Sort reports", - "filterButton": "Filter reports", - "generalCategory": "General", - "brainCategory": "Brain", - "heartCategory": "Heart" + "filterButton": "Filter reports" + }, + "filter": { + "title": "Filter", + "category": "Category", + "apply": "Apply" + }, + "category": { + "general": "General", + "heart": "Heart", + "brain": "Brain" } } diff --git a/frontend/src/common/utils/i18n/resources/es/report.json b/frontend/src/common/utils/i18n/resources/es/report.json index d1cf7e88..c2dc29b8 100644 --- a/frontend/src/common/utils/i18n/resources/es/report.json +++ b/frontend/src/common/utils/i18n/resources/es/report.json @@ -48,6 +48,21 @@ "filterAll": "Todos", "filterBookmarked": "Guardados", "noBookmarksTitle": "No Hay Informes Guardados", - "noBookmarksMessage": "Guarda informes como favoritos para encontrarlos rápidamente aquí" + "noBookmarksMessage": "Guarda informes como favoritos para encontrarlos rápidamente aquí", + "noMatchesTitle": "No Hay Informes Coincidentes", + "noMatchesMessage": "Ningún informe coincide con los filtros actuales. Intenta cambiar o eliminar los filtros.", + "clearFilters": "Eliminar Filtros", + "sortButton": "Ordenar informes", + "filterButton": "Filtrar informes" + }, + "filter": { + "title": "Filtrar", + "category": "Categoría", + "apply": "Aplicar" + }, + "category": { + "general": "General", + "heart": "Corazón", + "brain": "Cerebro" } } diff --git a/frontend/src/common/utils/i18n/resources/fr/report.json b/frontend/src/common/utils/i18n/resources/fr/report.json index e86d7a1f..1e034d34 100644 --- a/frontend/src/common/utils/i18n/resources/fr/report.json +++ b/frontend/src/common/utils/i18n/resources/fr/report.json @@ -48,6 +48,21 @@ "filterAll": "Tous", "filterBookmarked": "Favoris", "noBookmarksTitle": "Aucun Rapport en Favoris", - "noBookmarksMessage": "Marquez des rapports comme favoris pour les trouver rapidement ici" + "noBookmarksMessage": "Marquez des rapports comme favoris pour les trouver rapidement ici", + "noMatchesTitle": "Aucun Rapport Correspondant", + "noMatchesMessage": "Aucun rapport ne correspond à vos filtres actuels. Essayez de modifier ou de supprimer vos filtres.", + "clearFilters": "Effacer les Filtres", + "sortButton": "Trier les rapports", + "filterButton": "Filtrer les rapports" + }, + "filter": { + "title": "Filtrer", + "category": "Catégorie", + "apply": "Appliquer" + }, + "category": { + "general": "Général", + "heart": "Cœur", + "brain": "Cerveau" } } diff --git a/frontend/src/pages/Home/components/ReportItem/ReportItem.tsx b/frontend/src/pages/Home/components/ReportItem/ReportItem.tsx index a7f5da50..9c9975e2 100644 --- a/frontend/src/pages/Home/components/ReportItem/ReportItem.tsx +++ b/frontend/src/pages/Home/components/ReportItem/ReportItem.tsx @@ -39,13 +39,13 @@ const ReportItem: React.FC = ({ // Get category translation key based on category value const getCategoryTranslationKey = () => { if (categoryStr === ReportCategory.GENERAL.toLowerCase()) { - return 'list.generalCategory'; + return 'category.general'; } else if (categoryStr === ReportCategory.BRAIN.toLowerCase()) { - return 'list.brainCategory'; + return 'category.brain'; } else if (categoryStr === ReportCategory.HEART.toLowerCase()) { - return 'list.heartCategory'; + return 'category.heart'; } - return 'list.generalCategory'; // Default to general if not found + return 'category.general'; // Default to general if not found }; // Get the appropriate icon for the category From 2d9ac966b665d1f227a0b7426e02d8dd7631da0a Mon Sep 17 00:00:00 2001 From: Guido Percu Date: Tue, 22 Apr 2025 19:26:04 -0400 Subject: [PATCH 4/7] Improve filter modal --- .../src/pages/Reports/ReportsListPage.scss | 12 +++-- .../src/pages/Reports/ReportsListPage.tsx | 2 + .../components/CategoryTag/CategoryTag.scss | 23 ++++++---- .../components/FilterPanel/FilterPanel.scss | 45 ++++++++++++------- .../components/FilterPanel/FilterPanel.tsx | 23 +++++----- 5 files changed, 68 insertions(+), 37 deletions(-) diff --git a/frontend/src/pages/Reports/ReportsListPage.scss b/frontend/src/pages/Reports/ReportsListPage.scss index 3dfe1af8..20e6bd15 100644 --- a/frontend/src/pages/Reports/ReportsListPage.scss +++ b/frontend/src/pages/Reports/ReportsListPage.scss @@ -66,8 +66,10 @@ &__category-tags { display: flex; flex-wrap: wrap; - padding: 0.5rem 1rem; + padding: 0.75rem 1rem; margin-bottom: 0.5rem; + background-color: #f9f9f9; + border-bottom: 1px solid var(--ion-color-light); } &__content-container { @@ -112,10 +114,14 @@ } &__filter-modal { - --height: 80%; + --height: auto; --width: 100%; - --border-radius: 1rem 1rem 0 0; + --border-radius: 20px 20px 0 0; --box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1); + + &::part(content) { + border-radius: 20px 20px 0 0; + } } .skeleton { diff --git a/frontend/src/pages/Reports/ReportsListPage.tsx b/frontend/src/pages/Reports/ReportsListPage.tsx index 32fcc35c..5e7d75ee 100644 --- a/frontend/src/pages/Reports/ReportsListPage.tsx +++ b/frontend/src/pages/Reports/ReportsListPage.tsx @@ -322,6 +322,8 @@ const ReportsListPage: React.FC = () => { isOpen={showFilterModal} onDidDismiss={() => setShowFilterModal(false)} className="reports-list-page__filter-modal" + initialBreakpoint={0.9} + breakpoints={[0, 0.9]} > = ({

{t('filter.category', { ns: 'report' })}

- +
{categories.map((category) => ( - - {category.label} - handleCategoryToggle(category.id)} - /> - + ))} - +
From a777fa60a8f93f3936e1da4753e159f4d6205aa8 Mon Sep 17 00:00:00 2001 From: Guido Percu Date: Wed, 23 Apr 2025 09:32:51 -0400 Subject: [PATCH 5/7] Bookmark functionality in homepage --- frontend/src/pages/Home/HomePage.tsx | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/frontend/src/pages/Home/HomePage.tsx b/frontend/src/pages/Home/HomePage.tsx index 57bc4fde..8adc6431 100644 --- a/frontend/src/pages/Home/HomePage.tsx +++ b/frontend/src/pages/Home/HomePage.tsx @@ -14,6 +14,9 @@ import { useHistory } from 'react-router-dom'; import { useState } from 'react'; import { useGetLatestReports, useMarkReportAsRead } from 'common/hooks/useReports'; import { useCurrentUser } from 'common/hooks/useAuth'; +import { toggleReportBookmark } from 'common/api/reportService'; +import { useQueryClient } from '@tanstack/react-query'; +import { MedicalReport } from 'common/models/medicalReport'; import Avatar from 'common/components/Icon/Avatar'; import ReportItem from './components/ReportItem/ReportItem'; import NoReportsMessage from './components/NoReportsMessage/NoReportsMessage'; @@ -27,6 +30,7 @@ import './HomePage.scss'; const HomePage: React.FC = () => { const { t } = useTranslation('home'); const history = useHistory(); + const queryClient = useQueryClient(); const { data: reports, isLoading, isError } = useGetLatestReports(3); const { mutate: markAsRead } = useMarkReportAsRead(); const currentUser = useCurrentUser(); @@ -43,6 +47,31 @@ const HomePage: React.FC = () => { history.push(`/tabs/reports/${reportId}`); }; + const handleToggleBookmark = async (reportId: string, isCurrentlyBookmarked: boolean) => { + try { + // Toggle the bookmark status + const updatedReport = await toggleReportBookmark(reportId, !isCurrentlyBookmarked); + + // Update the reports in the cache + queryClient.setQueryData(['reports'], (oldReports) => { + if (!oldReports) return []; + return oldReports.map((report) => + report.id === updatedReport.id ? updatedReport : report, + ); + }); + + // Update the latest reports cache with the correct query key including the limit + queryClient.setQueryData(['latestReports', 3], (oldReports) => { + if (!oldReports) return []; + return oldReports.map((report) => + report.id === updatedReport.id ? updatedReport : report, + ); + }); + } catch (error) { + console.error('Failed to toggle bookmark:', error); + } + }; + const handleUpload = () => { history.push('/upload'); }; @@ -92,6 +121,7 @@ const HomePage: React.FC = () => { key={report.id} report={report} onClick={() => handleReportClick(report.id)} + onToggleBookmark={() => handleToggleBookmark(report.id, report.bookmarked)} showBookmarkButton={true} /> )); From ee48e8c3f36b73c2b57c916e82b49894b408d75d Mon Sep 17 00:00:00 2001 From: Guido Percu Date: Wed, 23 Apr 2025 09:36:37 -0400 Subject: [PATCH 6/7] Color in the filter button --- .../src/pages/Reports/components/FilterPanel/FilterPanel.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss b/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss index ba959776..79a26556 100644 --- a/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss +++ b/frontend/src/pages/Reports/components/FilterPanel/FilterPanel.scss @@ -42,9 +42,9 @@ cursor: pointer; &--selected { - background-color: #000000; + background-color: #313E4C; color: white; - border-color: #000000; + border-color: #313E4C; } } From 9ed541e2877aaf17a3c11289e3c47a3b0abd4605 Mon Sep 17 00:00:00 2001 From: Guido Percu Date: Wed, 23 Apr 2025 09:50:45 -0400 Subject: [PATCH 7/7] Improve CSS --- frontend/src/pages/Home/components/ReportItem/ReportItem.scss | 3 +-- frontend/src/pages/Reports/ReportsListPage.scss | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/pages/Home/components/ReportItem/ReportItem.scss b/frontend/src/pages/Home/components/ReportItem/ReportItem.scss index 822c850b..32eea10f 100644 --- a/frontend/src/pages/Home/components/ReportItem/ReportItem.scss +++ b/frontend/src/pages/Home/components/ReportItem/ReportItem.scss @@ -3,7 +3,6 @@ padding: 16px; border-radius: 12px; margin-bottom: 12px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); position: relative; cursor: pointer; @@ -67,6 +66,7 @@ &--active { color: white; background-color: #4355b9; + box-shadow: none; } } } @@ -81,7 +81,6 @@ flex-shrink: 0; background-color: white; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); - border: 1px solid rgba(0, 0, 0, 0.03); &--general, &--brain, diff --git a/frontend/src/pages/Reports/ReportsListPage.scss b/frontend/src/pages/Reports/ReportsListPage.scss index 20e6bd15..aa694bc8 100644 --- a/frontend/src/pages/Reports/ReportsListPage.scss +++ b/frontend/src/pages/Reports/ReportsListPage.scss @@ -3,7 +3,6 @@ &__header { box-shadow: none; - --background: var(--ion-background-color); ion-toolbar { --border-width: 0;