From 89bc8d00f7f65a0b9e32bbad799ade9fe481dde9 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Sun, 22 Mar 2026 18:23:15 -0400 Subject: [PATCH] v0.11.25 schedule list filter bug fixes --- backend/package.json | 2 +- build.sh | 2 +- frontend/package.json | 2 +- frontend/src/components/SchedulePage.jsx | 32 +++++++++++++++++++----- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/backend/package.json b/backend/package.json index 33d7cbd..6c01144 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.11.24", + "version": "0.11.25", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index 08d1a49..5b152b9 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.11.24}" +VERSION="${1:-0.11.25}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/package.json b/frontend/package.json index 5f17f43..133a81e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.11.24", + "version": "0.11.25", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/SchedulePage.jsx b/frontend/src/components/SchedulePage.jsx index 9bebf6b..45275fe 100644 --- a/frontend/src/components/SchedulePage.jsx +++ b/frontend/src/components/SchedulePage.jsx @@ -261,7 +261,7 @@ function MiniCalendar({ selected, onChange, eventDates=new Set() }) { } // ── Mobile Filter Bar (Schedule view: keyword+type filters with month nav; Day view: calendar accordion) ── -function MobileScheduleFilter({ selected, onMonthChange, view, eventTypes, filterKeyword, onFilterKeyword, filterTypeId, onFilterTypeId, filterAvailability=false, onFilterAvailability, eventDates=new Set(), onInputFocus, onInputBlur }) { +function MobileScheduleFilter({ selected, onMonthChange, view, eventTypes, filterKeyword, onFilterKeyword, filterTypeId, onFilterTypeId, filterAvailability=false, onFilterAvailability, onClearFromDate, eventDates=new Set(), onInputFocus, onInputBlur }) { // Day view: keep accordion calendar const [open, setOpen] = useState(false); const y=selected.getFullYear(), m=selected.getMonth(); @@ -337,7 +337,7 @@ function MobileScheduleFilter({ selected, onMonthChange, view, eventTypes, filte Requires Availability {hasFilters && ( - + )} )} @@ -982,11 +982,21 @@ function expandEvents(events, rangeStart, rangeEnd) { return result; } +// Parse keyword string into match descriptors. +// Quoted terms ("mount") -> exact whole-word match only. +// Unquoted terms (mount) -> word-boundary prefix: term must start a word, +// so "mount" matches "mountain" but "mounte" does not. function parseKeywords(raw) { const terms = []; const re = /"([^"]+)"|(\S+)/g; let match; - while((match = re.exec(raw)) !== null) terms.push((match[1]||match[2]).toLowerCase()); + while((match = re.exec(raw)) !== null) { + if (match[1] !== undefined) { + terms.push({ term: match[1].toLowerCase(), exact: true }); + } else { + terms.push({ term: match[2].toLowerCase(), exact: false }); + } + } return terms; } @@ -1028,7 +1038,16 @@ function ScheduleView({ events, selectedDate, onSelect, filterKeyword='', filter if(filterAvailability && !e.track_availability) return false; if(terms.length>0) { const haystack=[e.title||'',e.location||'',e.description||''].join(' ').toLowerCase(); - if(!terms.some(t=>haystack.includes(t))) return false; + const matches = ({ term, exact }) => { + if (exact) { + // Quoted: whole-word match only — term must be surrounded by word boundaries + return new RegExp('\\b' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b').test(haystack); + } else { + // Unquoted: prefix-of-word match — term must appear at the start of a word + return new RegExp('\\b' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).test(haystack); + } + }; + if(!terms.some(matches)) return false; } return true; }); @@ -1460,7 +1479,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel className="input" placeholder={`Keyword… (space = OR, "phrase")`} value={filterKeyword} - onChange={e => setFilterKeyword(e.target.value)} autoComplete="new-password" autoCorrect="off" autoCapitalize="off" spellCheck={false} + onChange={e => { setFilterKeyword(e.target.value); if (!e.target.value) setFilterFromDate(null); }} autoComplete="new-password" autoCorrect="off" autoCapitalize="off" spellCheck={false} style={{ marginBottom:8, fontSize:13 }} />