From 5ff3c19aba58afa3a05bbc3021b3cc41ecbe354f Mon Sep 17 00:00:00 2001 From: ChiragBellara Date: Tue, 23 Dec 2025 16:40:59 -0800 Subject: [PATCH 1/5] Added the Share and Filter icons --- .../Reports/Participation/NoShowInsights.jsx | 29 ++++++++++++------- .../Participation/Participation.module.css | 12 ++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx b/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx index ab635e8ad0..a92f9ed0ec 100644 --- a/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx +++ b/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { useSelector } from 'react-redux'; +import { ArrowUpDown, SquareArrowOutUpRight } from 'lucide-react'; import mockEvents from './mockData'; import styles from './Participation.module.css'; @@ -98,17 +99,23 @@ function NoShowInsights() { -
- {['Event type', 'Time', 'Location'].map(tab => ( - - ))} +
+
+ {['Event type', 'Time', 'Location'].map(tab => ( + + ))} +
+
+ + +
{renderStats()}
diff --git a/src/components/CommunityPortal/Reports/Participation/Participation.module.css b/src/components/CommunityPortal/Reports/Participation/Participation.module.css index f82ec50d11..caeb48cbb8 100644 --- a/src/components/CommunityPortal/Reports/Participation/Participation.module.css +++ b/src/components/CommunityPortal/Reports/Participation/Participation.module.css @@ -359,6 +359,7 @@ } .insightsTabs { + flex: 1; display: flex; border: 1px solid #ccc; border-radius: 5px; @@ -366,6 +367,17 @@ max-width: 60%; } +.insightsTabsContainer{ + display: flex; + align-items: center; + justify-content: space-between; +} + +.icons{ + display: flex; + gap: 8px; +} + .insightsTab { flex: 1; text-align: center; From b5f019fdb7ea475be12e513d18adaef4ad6beb9e Mon Sep 17 00:00:00 2001 From: ChiragBellara Date: Wed, 24 Dec 2025 14:59:20 -0800 Subject: [PATCH 2/5] Added sorting functionality and dark mode support for No-Show Insights --- .../Reports/Participation/NoShowInsights.jsx | 32 ++++++++++++++++--- .../Participation/Participation.module.css | 29 +++++++++++++---- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx b/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx index a92f9ed0ec..913e1af588 100644 --- a/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx +++ b/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx @@ -1,12 +1,13 @@ import { useState } from 'react'; import { useSelector } from 'react-redux'; -import { ArrowUpDown, SquareArrowOutUpRight } from 'lucide-react'; +import { ArrowUpDown, ArrowUp, ArrowDown, SquareArrowOutUpRight } from 'lucide-react'; import mockEvents from './mockData'; import styles from './Participation.module.css'; function NoShowInsights() { const [dateFilter, setDateFilter] = useState('All'); const [activeTab, setActiveTab] = useState('Event type'); + const [sortOrder, setSortOrder] = useState('none'); const darkMode = useSelector(state => state.theme.darkMode); const filterByDate = events => { @@ -34,6 +35,15 @@ function NoShowInsights() { }); }; + const handleSortClick = () => { + setSortOrder(prev => { + if (prev === 'none' || prev === 'desc') return 'asc'; + if (prev === 'asc') return 'desc'; + return 'none'; + }); + }; + const SortIcon = sortOrder === 'none' ? ArrowUpDown : sortOrder === 'asc' ? ArrowUp : ArrowDown; + const calculateStats = filteredEvents => { const statsMap = new Map(); @@ -65,8 +75,14 @@ function NoShowInsights() { const renderStats = () => { const filteredEvents = filterByDate(mockEvents); const stats = calculateStats(filteredEvents); + const finalStats = + sortOrder === 'none' + ? stats + : [...stats].sort((a, b) => + sortOrder === 'asc' ? a.percentage - b.percentage : b.percentage - a.percentage, + ); - return stats.map(item => ( + return finalStats.map(item => (
{item.label} @@ -100,12 +116,18 @@ function NoShowInsights() {
-
+
{['Event type', 'Time', 'Location'].map(tab => (
- +
diff --git a/src/components/CommunityPortal/Reports/Participation/Participation.module.css b/src/components/CommunityPortal/Reports/Participation/Participation.module.css index caeb48cbb8..608caa0136 100644 --- a/src/components/CommunityPortal/Reports/Participation/Participation.module.css +++ b/src/components/CommunityPortal/Reports/Participation/Participation.module.css @@ -374,6 +374,7 @@ } .icons{ + cursor: pointer; display: flex; gap: 8px; } @@ -383,13 +384,29 @@ text-align: center; padding: 10px 0; font-size: 0.9rem; - color: #555; - background-color: #f8f9fa; border: none; cursor: pointer; transition: background-color 0.3s, color 0.3s; } +.insightsTabLight{ + color: #555; + background-color: #f8f9fa; +} + +.insightsTabDark{ + color: #f8f9fa; + background-color: #1C2541; +} + +.insightsTabDark:hover { + background-color: #3A506B; +} + +.insightsTabLight:hover { + background-color: #e0e0e0; +} + .insightsTab:not(:last-child) { border-right: 1px solid #ccc; } @@ -401,10 +418,6 @@ font-weight: bold; } -.insightsTab:hover { - background-color: #e0e0e0; -} - .insightsContent { display: flex; flex-direction: column; @@ -459,6 +472,10 @@ color: #333; } +.insightsPercentageDark { + color: #ffffff; + } + /* ---------- PDF/Print helpers ---------- */ .pageBreakBefore { break-before: page; page-break-before: always; } .pageBreakAfter { break-after: page; page-break-after: always; } From 68a5bb8cafa0e5a05adaddd626a63e853ac000bf Mon Sep 17 00:00:00 2001 From: ChiragBellara Date: Fri, 26 Dec 2025 10:11:12 -0800 Subject: [PATCH 3/5] Fixed select dropdown in dark mode and changed total width to sync light and dark modes --- .../Reports/Participation/NoShowInsights.jsx | 2 +- .../Reports/Participation/Participation.module.css | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx b/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx index 913e1af588..a5cf0066c6 100644 --- a/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx +++ b/src/components/CommunityPortal/Reports/Participation/NoShowInsights.jsx @@ -105,7 +105,7 @@ function NoShowInsights() {

No-show rate insights

-
+
setDateFilter(e.target.value)}> - - - - - -
-
+ const getPdfFilename = () => { + const now = new Date(); + const localDate = now.toLocaleDateString('en-CA'); + const filename = `no-show-insights_${dateFilter}_${activeTab}_${localDate}.pdf`; + return filename.replace(/\s+/g, '_').toLowerCase(); + }; + + const handleDownloadPdf = async () => { + try { + setIsExporting(true); + setExportError(''); + const pdf = await buildPdfFromView(); + pdf.save(getPdfFilename()); + setIsExportOpen(false); + } catch (e) { + setExportError(e?.message || 'Failed to download PDF.'); + } finally { + setIsExporting(false); + } + }; + + const handleSharePdf = async () => { + try { + setIsExporting(true); + setExportError(''); + + const pdf = await buildPdfFromView(); + const blob = pdf.output('blob'); + const file = new File([blob], getPdfFilename(), { type: 'application/pdf' }); + + if (!navigator.share || !navigator.canShare?.({ files: [file] })) { + setExportError( + 'Sharing is not supported in this browser. Please download the PDF instead.', + ); + return; + } + + await navigator.share({ + title: 'No-show rate insights', + text: `Insights (${dateFilter}, ${activeTab})`, + files: [file], + }); + + setIsExportOpen(false); + } catch (e) { + setExportError(e?.message || 'Failed to share PDF.'); + } finally { + setIsExporting(false); + } + }; -
+ return ( + <> + {isExportOpen && (
!isExporting && setIsExportOpen(false)} + onKeyDown={() => !isExporting && setIsExportOpen(false)} + role="button" + tabIndex={0} > - {['Event type', 'Time', 'Location'].map(tab => ( - - ))} +
e.stopPropagation()} + onKeyDown={e => e.stopPropagation()} + role="button" + tabIndex={0} + > +
+

Export No-show Insights

+ +
+ +
+
+
+ Filter: {dateFilter} +
+
+ View: {activeTab} +
+
+ + {exportError &&
{exportError}
} + +
+ + + +
+
+
+
+ )} +
+
+

No-show rate insights

+
+ +
-
-
- - - {sortOrder === 'none' ? 'Default' : sortOrder === 'asc' ? 'Low → High' : 'High → Low'} - + +
+
+ {['Event type', 'Time', 'Location'].map(tab => ( + + ))}
-
- - Export Data +
+
+ + + {sortOrder === 'none' + ? 'Default' + : sortOrder === 'asc' + ? 'Low → High' + : 'High → Low'} + +
+
+ { + setExportError(''); + setIsExportOpen(true); + }} + /> + Export Data +
-
-
{renderStats()}
-
+
{renderStats()}
+
+ ); } diff --git a/src/components/CommunityPortal/Reports/Participation/Participation.module.css b/src/components/CommunityPortal/Reports/Participation/Participation.module.css index c6b2976b52..d011f0abf1 100644 --- a/src/components/CommunityPortal/Reports/Participation/Participation.module.css +++ b/src/components/CommunityPortal/Reports/Participation/Participation.module.css @@ -508,6 +508,105 @@ opacity: 1; } +/* ---------- Export PDF modal ---------- */ +.modalOverlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.45); + display: flex; + justify-content: center; + align-items: center; + z-index: 999; +} + +.modal { + width: 420px; + max-width: calc(100vw - 32px); + background: #fff; + border-radius: 12px; + padding: 16px; +} + +.modalDark { + background: #1C2541; +} + +.modalHeader { + display: flex; + align-items: center; + justify-content: space-between; +} + +.modalTitle { + margin: 0; +} + +.modalClose { + border: none; + background: transparent; + font-size: 22px; + cursor: pointer; +} + +.modalBody { + margin-top: 12px; +} + +.modalMeta { + font-size: 16px; + opacity: 0.85; + display: grid; + gap: 6px; + margin-bottom: 12px; +} + +.modalError { + margin-bottom: 12px; + font-size: 13px; + color: #b00020; +} + +.modalActions { + display: flex; + gap: 10px; +} + +.exportOptionsButtons, +.exportOptionsButtonsDark { + flex: 1; + padding: 10px 12px; + border-radius: 10px; + cursor: pointer; +} + +.exportOptionsButtons{ + background: transparent; + border: 1px solid #ccc; + color: #555; +} + +.exportOptionsButtons:hover{ + color: #555; + background-color: #e0e0e0; +} + +.exportOptionsButtonsDark{ + border: 1px solid #ccc; + color: #111; +} + +.exportOptionsButtonsDark:hover { + background: #3A506B; +} + +.exportOptionsButtons:disabled, +.exportOptionsButtonsDark:disabled { + opacity: 0.6; + cursor: not-allowed; +} + + + /* ---------- PDF/Print helpers ---------- */ .pageBreakBefore { break-before: page; page-break-before: always; } .pageBreakAfter { break-after: page; page-break-after: always; }