v0.9.85 minor ui updates

This commit is contained in:
2026-03-19 10:12:04 -04:00
parent 71575f278e
commit 6b7d555879
6 changed files with 69 additions and 21 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.84
JAMA_VERSION=0.9.85
# App port — the host port Docker maps to the container
PORT=3000

View File

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

View File

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

View File

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

View File

@@ -52,9 +52,9 @@ const RESP_ICON = {
),
};
const BELL_ICON = (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="#ef4444" width={15} height={15} style={{flexShrink:0}}>
<svg xmlns="http://www.w3.org/2000/svg" fill="#fbbf24" viewBox="0 0 24 24" strokeWidth={1.5} stroke="var(--warning-stroke)" width={15} height={15} style={{flexShrink:0}}>
<title>Awaiting your response</title>
<path strokeLinecap="round" strokeLinejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" />
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
</svg>
);
@@ -715,9 +715,12 @@ function ScheduleView({ events, selectedDate, onSelect, filterKeyword='', filter
const today=new Date(); today.setHours(0,0,0,0);
const terms=parseKeywords(filterKeyword);
const hasFilters = terms.length > 0 || !!filterTypeId;
// No filters: show selected month only (desktop and mobile — month nav arrows control the month).
// Filters active: show today + all future on both platforms.
const from = hasFilters ? today : new Date(y,m,1);
const now = new Date(); // exact now for end-time comparison
const isCurrentMonth = y === today.getFullYear() && m === today.getMonth();
// No filters: show from today (if current month) or start of month (future months) to end of month.
// Past months show nothing — navigate forward to see events.
// Filters active: today + all future events.
const from = hasFilters ? today : (isCurrentMonth ? today : new Date(y,m,1));
const to = hasFilters ? new Date(9999,11,31) : new Date(y,m+1,0,23,59,59);
const filtered=events.filter(e=>{
const s=new Date(e.start_at);
@@ -729,19 +732,62 @@ function ScheduleView({ events, selectedDate, onSelect, filterKeyword='', filter
}
return true;
});
const emptyMsg = hasFilters ? 'No events match your filters' : `No events in ${MONTHS[m]} ${y}`;
const emptyMsg = hasFilters
? 'No events match your filters'
: new Date(y,m+1,0) < today
? `No events — ${MONTHS[m]} ${y} is in the past`
: `No events in ${MONTHS[m]} ${y}`;
if(!filtered.length) return <div style={{textAlign:'center',padding:'60px 20px',color:'var(--text-tertiary)',fontSize:14}}>{emptyMsg}</div>;
return <>{filtered.map(e=>{const s=new Date(e.start_at);const col=e.event_type?.colour||'#9ca3af';
// Desktop: original pre-v0.9.64 sizes. Mobile: compact sizes from v0.9.64
const rowPad=isMobile?'12px 14px':'14px 20px';
const rowGap=isMobile?10:20;
const datW=isMobile?36:44; const datFs=isMobile?20:22; const datSFs=isMobile?10:11;
const timeW=isMobile?62:100; const timeGap=isMobile?5:8; const timeFs=isMobile?11:13;
const dotSz=isMobile?8:10;
const typeFs=isMobile?10:11; const titleGap=isMobile?6:8;
return(<div key={e.id} onClick={()=>onSelect(e)} style={{display:'flex',alignItems:'center',gap:rowGap,padding:rowPad,borderBottom:'1px solid var(--border)',cursor:'pointer'}} onMouseEnter={el=>el.currentTarget.style.background='var(--background)'} onMouseLeave={el=>el.currentTarget.style.background=''}><div style={{width:datW,textAlign:'center',flexShrink:0}}><div style={{fontSize:datFs,fontWeight:700,lineHeight:1}}>{s.getDate()}</div><div style={{fontSize:datSFs,color:'var(--text-tertiary)',textTransform:'uppercase'}}>{SHORT_MONTHS[s.getMonth()]}, {DAYS[s.getDay()]}</div></div><div style={{width:timeW,flexShrink:0,display:'flex',alignItems:'flex-start',gap:timeGap,fontSize:timeFs,color:'var(--text-secondary)'}}><span style={{width:dotSz,height:dotSz,borderRadius:'50%',background:col,flexShrink:0,marginTop:3}}/>{e.all_day?<span>All day</span>:<span style={{lineHeight:1.5}}>{fmtTime(e.start_at)} <br/>{fmtTime(e.end_at)}</span>}</div><div style={{flex:1,minWidth:0}}><div style={{fontSize:14,fontWeight:600,display:'flex',alignItems:'center',gap:titleGap,flexWrap:'nowrap'}}>{e.event_type?.name&&<span style={{fontSize:typeFs,color:'var(--text-tertiary)',textTransform:'uppercase',letterSpacing:'0.5px',fontWeight:600,flexShrink:0}}>{e.event_type.name}:</span>}<span style={{minWidth:0,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{e.title}</span>{!!e.track_availability&&(
e.my_response ? RESP_ICON[e.my_response](RESP_COLOR[e.my_response]) : BELL_ICON
)}</div>{e.location&&<div style={{fontSize:12,color:'var(--text-tertiary)',marginTop:2}}>{e.location}</div>}</div></div>);})}</>;
return <>{filtered.map(e=>{
const s=new Date(e.start_at);
const end=new Date(e.end_at);
const isPast = !e.all_day && end < now; // event fully ended
const col = isPast ? '#9ca3af' : (e.event_type?.colour||'#9ca3af');
const textColor = isPast ? 'var(--text-tertiary)' : 'var(--text-primary)';
const subColor = isPast ? 'var(--text-tertiary)' : 'var(--text-secondary)';
// Use CSS media query breakpoint logic — compact below 640px regardless of isMobile prop
// so responsive desktop doesn't compact when there's plenty of room
const compact = isMobile; // isMobile is only true on genuine mobile, not responsive desktop
const rowPad=compact?'12px 14px':'14px 20px';
const rowGap=compact?10:20;
const datW=compact?36:44; const datFs=compact?20:22; const datSFs=compact?10:11;
const timeW=compact?80:100; const timeGap=compact?5:8; const timeFs=compact?11:13;
const dotSz=compact?8:10;
const availIcon = !!e.track_availability && (
e.my_response
? RESP_ICON[e.my_response](isPast ? '#9ca3af' : RESP_COLOR[e.my_response])
: isPast
? <svg xmlns="http://www.w3.org/2000/svg" fill="#d97706" viewBox="0 0 24 24" strokeWidth={1.5} stroke="var(--warning-stroke)" width={15} height={15} style={{flexShrink:0,opacity:0.5}}><path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"/></svg>
: BELL_ICON
);
return(
<div key={e.id} onClick={()=>onSelect(e)} style={{display:'flex',alignItems:'center',gap:rowGap,padding:rowPad,borderBottom:'1px solid var(--border)',cursor:'pointer',opacity:isPast?0.7:1}} onMouseEnter={el=>el.currentTarget.style.background='var(--background)'} onMouseLeave={el=>el.currentTarget.style.background=''}>
{/* Date column */}
<div style={{width:datW,textAlign:'center',flexShrink:0}}>
<div style={{fontSize:datFs,fontWeight:700,lineHeight:1,color:textColor}}>{s.getDate()}</div>
<div style={{fontSize:datSFs,color:'var(--text-tertiary)',textTransform:'uppercase'}}>{SHORT_MONTHS[s.getMonth()]}, {DAYS[s.getDay()]}</div>
</div>
{/* Time + dot column */}
<div style={{width:timeW,flexShrink:0,display:'flex',alignItems:'flex-start',gap:timeGap,fontSize:timeFs,color:subColor}}>
<span style={{width:dotSz,height:dotSz,borderRadius:'50%',background:col,flexShrink:0,marginTop:3}}/>
{e.all_day?<span>All day</span>:<span style={{lineHeight:1.5}}>{fmtTime(e.start_at)} <br/>{fmtTime(e.end_at)}</span>}
</div>
{/* Title + meta column */}
<div style={{flex:1,minWidth:0}}>
<div style={{display:'flex',alignItems:'center',gap:8,minWidth:0}}>
<span style={{fontSize:14,fontWeight:600,color:textColor,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',flex:1,minWidth:0}}>{e.title}</span>
{availIcon}
</div>
{(e.event_type?.name||e.location) && (
<div style={{fontSize:12,color:'var(--text-tertiary)',marginTop:2,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>
{e.event_type?.name&&<span style={{textTransform:'uppercase',letterSpacing:'0.4px',fontWeight:600,marginRight:e.location?6:0}}>{e.event_type.name}{e.location?' ·':''}</span>}
{e.location&&<span>{e.location}</span>}
</div>
)}
</div>
</div>
);
})}</>;
}
const HOUR_H = 52; // px per hour row

View File

@@ -7,6 +7,7 @@
--primary-dark: #1557b0;
--primary-light: #e8f0fe;
--surface: #ffffff;
--warning-stroke: #92400e; /* amber-900 — dark stroke on yellow fill */
--surface-variant: #f8f9fa;
--background: #f1f3f4;
--border: #e0e0e0;
@@ -211,6 +212,7 @@ a { color: inherit; text-decoration: none; }
--primary-dark: #4d9de0;
--primary-light: #1a2d4a;
--surface: #1e1e2e;
--warning-stroke: #fde68a; /* amber-200 — light stroke on yellow fill in dark mode */
--surface-variant: #252535;
--background: #13131f;
--border: #2e2e45;