v0.9.81 bugs fixes

This commit is contained in:
2026-03-18 19:14:34 -04:00
parent de22432cc5
commit ca2d472837
6 changed files with 27 additions and 23 deletions

View File

@@ -10,7 +10,7 @@
PROJECT_NAME=jama
# Image version to run (set by build.sh, or use 'latest')
JAMA_VERSION=0.9.80
JAMA_VERSION=0.9.81
# App port — the host port Docker maps to the container
PORT=3000

View File

@@ -1,6 +1,6 @@
{
"name": "jama-backend",
"version": "0.9.80",
"version": "0.9.81",
"description": "TeamChat backend server",
"main": "src/index.js",
"scripts": {

View File

@@ -13,7 +13,7 @@
# ─────────────────────────────────────────────────────────────
set -euo pipefail
VERSION="${1:-0.9.80}"
VERSION="${1:-0.9.81}"
ACTION="${2:-}"
REGISTRY="${REGISTRY:-}"
IMAGE_NAME="jama"

View File

@@ -1,6 +1,6 @@
{
"name": "jama-frontend",
"version": "0.9.80",
"version": "0.9.81",
"private": true,
"scripts": {
"dev": "vite",

View File

@@ -277,7 +277,7 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
<div style={{ flex:1,overflowY:'auto' }}>
{/* Title */}
<div style={{ padding:'16px 20px',borderBottom:'1px solid var(--border)' }}>
<input value={title} onChange={e=>setTitle(e.target.value)} placeholder="Add title" style={{ width:'100%',border:'none',background:'transparent',fontSize:22,fontWeight:700,color:'var(--text-primary)',outline:'none' }}/>
<input value={title} onChange={e=>setTitle(e.target.value)} placeholder="Add title" autoComplete="off" autoCorrect="off" autoCapitalize="sentences" spellCheck={false} style={{ width:'100%',border:'none',background:'transparent',fontSize:22,fontWeight:700,color:'var(--text-primary)',outline:'none' }}/>
</div>
{/* Event Type */}
@@ -359,12 +359,12 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
{/* Location */}
<MobileRow icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>}>
<input value={location} onChange={e=>setLocation(e.target.value)} placeholder="Add location" style={{ width:'100%',border:'none',background:'transparent',fontSize:15,color:'var(--text-primary)',outline:'none' }}/>
<input value={location} onChange={e=>setLocation(e.target.value)} placeholder="Add location" autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck={false} style={{ width:'100%',border:'none',background:'transparent',fontSize:15,color:'var(--text-primary)',outline:'none' }}/>
</MobileRow>
{/* Description */}
<MobileRow icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="21" y1="10" x2="3" y2="10"/><line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="14" x2="3" y2="14"/><line x1="21" y1="18" x2="3" y2="18"/></svg>} border={false}>
<textarea value={description} onChange={e=>setDescription(e.target.value)} placeholder="Add description" rows={3} style={{ width:'100%',border:'none',background:'transparent',fontSize:15,color:'var(--text-primary)',outline:'none',resize:'none' }}/>
<textarea value={description} onChange={e=>setDescription(e.target.value)} placeholder="Add description" rows={3} autoComplete="off" autoCorrect="off" spellCheck={false} style={{ width:'100%',border:'none',background:'transparent',fontSize:15,color:'var(--text-primary)',outline:'none',resize:'none' }}/>
</MobileRow>
{/* Delete */}
@@ -394,7 +394,7 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
value={newTypeName}
onChange={e=>setNewTypeName(e.target.value)}
onKeyDown={e=>e.key==='Enter'&&createEventType()}
placeholder="Type name…"
placeholder="Type name…" autoComplete="off" autoCorrect="off" autoCapitalize="words" spellCheck={false}
style={{ width:'100%',padding:'12px 14px',border:'1px solid var(--border)',borderRadius:'var(--radius)',fontSize:16,marginBottom:12,boxSizing:'border-box',background:'var(--background)',color:'var(--text-primary)' }}
/>
<div style={{ display:'flex',alignItems:'center',gap:12,marginBottom:16 }}>

View File

@@ -159,6 +159,7 @@ function MobileScheduleFilter({ selected, onMonthChange, view, eventTypes, filte
value={filterKeyword}
onChange={e=>onFilterKeyword(e.target.value)}
placeholder="Search events…"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck={false}
style={{width:'100%',padding:'7px 8px 7px 28px',border:'1px solid var(--border)',borderRadius:'var(--radius)',background:'var(--background)',color:'var(--text-primary)',fontSize:13,boxSizing:'border-box'}}
/>
</div>
@@ -806,17 +807,18 @@ function DayView({ events, selectedDate, onSelect, onSwipe }) {
const handleTouchEnd = e => {
const dx = e.changedTouches[0].clientX - touchRef.current.x;
const dy = Math.abs(e.changedTouches[0].clientY - touchRef.current.y);
// Require horizontal swipe > 60px, not too vertical, and not from left edge (< 30px = back gesture)
if(Math.abs(dx) > 60 && dy < 80 && touchRef.current.x > 30) {
// Only trigger horizontal swipe if clearly horizontal (dx > dy) and > 60px
// and not from left edge (< 30px = OS back gesture)
if(Math.abs(dx) > 60 && Math.abs(dx) > dy * 1.5 && touchRef.current.x > 30) {
onSwipe?.(dx < 0 ? 1 : -1); // left = next day, right = prev day
}
};
return(
<div style={{display:'flex',flexDirection:'column',height:'100%'}} onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd}>
<div style={{display:'flex',flexDirection:'column',height:'100%',touchAction:'pan-y'}} onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd}>
<div style={{display:'flex',borderBottom:'1px solid var(--border)',padding:'8px 0 8px 60px',fontSize:13,fontWeight:600,color:'var(--primary)',flexShrink:0}}>
<div style={{textAlign:'center'}}><div>{DAYS[selectedDate.getDay()]}</div><div style={{fontSize:28,fontWeight:700}}>{selectedDate.getDate()}</div></div>
</div>
<div ref={scrollRef} style={{flex:1,overflowY:'auto',position:'relative'}}>
<div ref={scrollRef} style={{flex:1,overflowY:'auto',position:'relative',touchAction:'pan-y'}}>
<div style={{position:'relative'}}>
{hours.map(h=>(
<div key={h} style={{display:'flex',borderBottom:'1px solid var(--border)',height:HOUR_H}}>
@@ -861,20 +863,21 @@ function WeekView({ events, selectedDate, onSelect }) {
const handleTouchEnd = e => {
const dx = e.changedTouches[0].clientX - touchRef.current.x;
const dy = Math.abs(e.changedTouches[0].clientY - touchRef.current.y);
// Require horizontal swipe > 60px, not too vertical, and not from left edge (< 30px = back gesture)
if(Math.abs(dx) > 60 && dy < 80 && touchRef.current.x > 30) {
// Only trigger horizontal swipe if clearly horizontal (dx > dy) and > 60px
// and not from left edge (< 30px = OS back gesture)
if(Math.abs(dx) > 60 && Math.abs(dx) > dy * 1.5 && touchRef.current.x > 30) {
onSwipe?.(dx < 0 ? 1 : -1); // left = next day, right = prev day
}
};
return(
<div style={{display:'flex',flexDirection:'column',height:'100%'}} onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd}>
<div style={{display:'flex',flexDirection:'column',height:'100%',touchAction:'pan-y'}} onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd}>
{/* Day headers */}
<div style={{display:'grid',gridTemplateColumns:'60px repeat(7,1fr)',borderBottom:'1px solid var(--border)',background:'var(--surface)',flexShrink:0}}>
<div/>
{days.map((d,i)=><div key={i} style={{textAlign:'center',padding:'6px 4px',fontSize:12,fontWeight:600,color:sameDay(d,today)?'var(--primary)':'var(--text-secondary)'}}>{DAYS[d.getDay()]} {d.getDate()}</div>)}
</div>
{/* Scrollable time grid */}
<div ref={scrollRef} style={{flex:1,overflowY:'auto'}}>
<div ref={scrollRef} style={{flex:1,overflowY:'auto',touchAction:'pan-y'}}>
<div style={{display:'grid',gridTemplateColumns:'60px repeat(7,1fr)',position:'relative'}}>
{/* Time labels column */}
<div>
@@ -1049,8 +1052,8 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
</button>
{createOpen && (
<div style={{ position:'absolute', top:'100%', left:0, right:0, zIndex:100, background:'var(--surface-variant)', border:'1px solid var(--border)', borderRadius:'var(--radius)', marginTop:4, boxShadow:'0 4px 16px rgba(0,0,0,0.18)' }}>
{[['Event', ()=>{setPanel('eventForm');setEditingEvent(null);setCreateOpen(false);}],
['Event Type', ()=>{setPanel('eventTypes');setCreateOpen(false);}],
{[['Event', ()=>{setPanel('eventForm');setEditingEvent(null);setCreateOpen(false);setFilterKeyword('');setFilterTypeId('');}],
['Event Type', ()=>{setPanel('eventTypes');setCreateOpen(false);setFilterKeyword('');setFilterTypeId('');}],
['Bulk Event Import', ()=>{setPanel('bulkImport');setCreateOpen(false);}]
].map(([label,action])=>(
<button key={label} onClick={action} style={{display:'block',width:'100%',padding:'9px 16px',textAlign:'left',fontSize:14,background:'none',border:'none',cursor:'pointer',color:'var(--text-primary)'}}
@@ -1077,6 +1080,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
placeholder={`Keyword… (space = OR, "phrase")`}
value={filterKeyword}
onChange={e=>setFilterKeyword(e.target.value)}
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck={false}
style={{ marginBottom:8, fontSize:13 }}
/>
<select
@@ -1128,7 +1132,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
{allowedViews.map(v => {
const labels = { schedule:'Schedule', day:'Day', week:'Week', month:'Month' };
return (
<button key={v} onClick={()=>{setView(v);setPanel('calendar');}} style={{ padding:'4px 10px', borderRadius:5, border:'none', cursor:'pointer', fontSize:12, fontWeight:600, background:view===v?'var(--surface)':'transparent', color:view===v?'var(--text-primary)':'var(--text-tertiary)', boxShadow:view===v?'0 1px 3px rgba(0,0,0,0.1)':'none', transition:'all 0.15s', whiteSpace:'nowrap' }}>
<button key={v} onClick={()=>{setView(v);setPanel('calendar');setSelDate(new Date());setFilterKeyword('');setFilterTypeId('');}} style={{ padding:'4px 10px', borderRadius:5, border:'none', cursor:'pointer', fontSize:12, fontWeight:600, background:view===v?'var(--surface)':'transparent', color:view===v?'var(--text-primary)':'var(--text-tertiary)', boxShadow:view===v?'0 1px 3px rgba(0,0,0,0.1)':'none', transition:'all 0.15s', whiteSpace:'nowrap' }}>
{labels[v]}
</button>
);
@@ -1165,7 +1169,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
<div style={{ padding:28, maxWidth:1024 }}>
<h2 style={{ fontSize:17, fontWeight:700, marginBottom:24 }}>{editingEvent?'Edit Event':'New Event'}</h2>
<EventForm event={editingEvent} userGroups={userGroups} eventTypes={eventTypes} selectedDate={selDate} isToolManager={isToolManager}
onSave={handleSaved} onCancel={()=>{setPanel('calendar');setEditingEvent(null);}} onDelete={handleDelete}/>
onSave={handleSaved} onCancel={()=>{setPanel('calendar');setEditingEvent(null);setFilterKeyword('');setFilterTypeId('');}} onDelete={handleDelete}/>
</div>
)}
@@ -1212,7 +1216,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
selectedDate={selDate}
isToolManager={isToolManager}
onSave={handleSaved}
onCancel={()=>{setPanel('calendar');setEditingEvent(null);}}
onCancel={()=>{setPanel('calendar');setEditingEvent(null);setFilterKeyword('');setFilterTypeId('');}}
onDelete={handleDelete}
/>
</div>
@@ -1228,8 +1232,8 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
</button>
{createOpen && (
<div style={{ position:'absolute', bottom:'calc(100% + 8px)', right:0, zIndex:100, background:'var(--surface-variant)', border:'1px solid var(--border)', borderRadius:'var(--radius)', boxShadow:'0 -4px 16px rgba(0,0,0,0.15)', minWidth:180 }}>
{[['Event', ()=>{setPanel('eventForm');setEditingEvent(null);setCreateOpen(false);}],
['Event Type', ()=>{setPanel('eventTypes');setCreateOpen(false);}],
{[['Event', ()=>{setPanel('eventForm');setEditingEvent(null);setCreateOpen(false);setFilterKeyword('');setFilterTypeId('');}],
['Event Type', ()=>{setPanel('eventTypes');setCreateOpen(false);setFilterKeyword('');setFilterTypeId('');}],
].map(([label,action])=>(
<button key={label} onClick={action} style={{display:'block',width:'100%',padding:'12px 16px',textAlign:'left',fontSize:15,background:'none',border:'none',cursor:'pointer',color:'var(--text-primary)',borderBottom:'1px solid var(--border)'}}
onMouseEnter={e=>e.currentTarget.style.background='var(--background)'} onMouseLeave={e=>e.currentTarget.style.background=''}>{label}</button>