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 }} />