v0.12.10 ui bug fixes

This commit is contained in:
2026-03-23 22:34:42 -04:00
parent 477b25dfa0
commit bcd9f4a060
5 changed files with 42 additions and 26 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "rosterchirp-backend", "name": "rosterchirp-backend",
"version": "0.12.9", "version": "0.12.10",
"description": "RosterChirp backend server", "description": "RosterChirp backend server",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {

View File

@@ -13,7 +13,7 @@
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
set -euo pipefail set -euo pipefail
VERSION="${1:-0.12.9}" VERSION="${1:-0.12.10}"
ACTION="${2:-}" ACTION="${2:-}"
REGISTRY="${REGISTRY:-}" REGISTRY="${REGISTRY:-}"
IMAGE_NAME="rosterchirp" IMAGE_NAME="rosterchirp"

View File

@@ -1,6 +1,6 @@
{ {
"name": "rosterchirp-frontend", "name": "rosterchirp-frontend",
"version": "0.12.9", "version": "0.12.10",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -21,7 +21,9 @@ import Avatar from '../components/Avatar.jsx';
function UserCheckList({ allUsers, selectedIds, onChange, onIF, onIB }) { function UserCheckList({ allUsers, selectedIds, onChange, onIF, onIB }) {
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const filtered = allUsers.filter(u => (u.display_name||u.name).toLowerCase().includes(search.toLowerCase())); const filtered = allUsers
.filter(u => (u.display_name||u.name).toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => (a.display_name||a.name).localeCompare(b.display_name||b.name));
return ( return (
<div> <div>
<input className="input" placeholder="Search users…" value={search} onChange={e => setSearch(e.target.value)} autoComplete="new-password" style={{ marginBottom:8 }} autoComplete="new-password" onFocus={onIF} onBlur={onIB} /> <input className="input" placeholder="Search users…" value={search} onChange={e => setSearch(e.target.value)} autoComplete="new-password" style={{ marginBottom:8 }} autoComplete="new-password" onFocus={onIF} onBlur={onIB} />

View File

@@ -356,33 +356,47 @@ export default function UserManagerPage({ isMobile = false, onProfile, onHelp, o
)} )}
{/* Content */} {/* Content */}
<div style={{ flex:1, overflowY:'auto', padding:16, paddingBottom: isMobile ? 72 : 16, overscrollBehavior: 'contain' }}> <div style={{ flex:1, display:'flex', flexDirection:'column', overflow:'hidden', minHeight:0, background:'var(--background)' }}>
{tab === 'users' && ( {tab === 'users' && (
<> <>
<input className="input" placeholder="Search users…" value={search} onChange={e => setSearch(e.target.value)} {/* Search — always visible, outside scroll area */}
onFocus={onIF} onBlur={onIB} <div style={{ padding:'16px 16px 8px', flexShrink:0 }}>
autoComplete="new-password" autoCorrect="off" spellCheck={false} <input className="input" placeholder="Search users…" value={search} onChange={e => setSearch(e.target.value)}
style={{ marginBottom:16, width:'100%', maxWidth: isMobile ? '100%' : 400 }} /> onFocus={onIF} onBlur={onIB}
<div style={{ background:'var(--surface)', borderRadius:'var(--radius)', boxShadow:'var(--shadow-sm)', overflow:'hidden' }}> autoComplete="new-password" autoCorrect="off" spellCheck={false}
{loading ? ( style={{ width:'100%', maxWidth: isMobile ? '100%' : 400 }} />
<div style={{ padding:48, textAlign:'center' }}><div className="spinner" /></div> </div>
) : loadError ? ( {/* User list — bounded scroll */}
<div style={{ padding:32, textAlign:'center', color:'var(--error)' }}> <div style={{ flex:1, overflowY:'auto', padding:'0 16px', paddingBottom: isMobile ? 72 : 16, overscrollBehavior:'contain' }}>
<div style={{ marginBottom:12 }}> {loadError}</div> <div style={{ background:'var(--surface)', borderRadius:'var(--radius)', boxShadow:'var(--shadow-sm)', overflow:'hidden' }}>
<button className="btn btn-secondary btn-sm" onClick={load}>Retry</button> {loading ? (
</div> <div style={{ padding:48, textAlign:'center' }}><div className="spinner" /></div>
) : filtered.length === 0 ? ( ) : loadError ? (
<div style={{ padding:32, textAlign:'center', color:'var(--text-tertiary)', fontSize:14 }}> <div style={{ padding:32, textAlign:'center', color:'var(--error)' }}>
{search ? 'No users match your search.' : 'No users yet.'} <div style={{ marginBottom:12 }}> {loadError}</div>
</div> <button className="btn btn-secondary btn-sm" onClick={load}>Retry</button>
) : ( </div>
filtered.map(u => <UserRow key={u.id} u={u} onUpdated={load} />) ) : filtered.length === 0 ? (
)} <div style={{ padding:32, textAlign:'center', color:'var(--text-tertiary)', fontSize:14 }}>
{search ? 'No users match your search.' : 'No users yet.'}
</div>
) : (
filtered.map(u => <UserRow key={u.id} u={u} onUpdated={load} />)
)}
</div>
</div> </div>
</> </>
)} )}
{tab === 'create' && <CreateUserForm userPass={userPass} onCreated={() => { load(); setTab('users'); }} isMobile={isMobile} onIF={onIF} onIB={onIB} />} {tab === 'create' && (
{tab === 'bulk' && <BulkImportForm userPass={userPass} onCreated={load} />} <div style={{ flex:1, overflowY:'auto', padding:16, paddingBottom: isMobile ? 72 : 16, overscrollBehavior:'contain' }}>
<CreateUserForm userPass={userPass} onCreated={() => { load(); setTab('users'); }} isMobile={isMobile} onIF={onIF} onIB={onIB} />
</div>
)}
{tab === 'bulk' && (
<div style={{ flex:1, overflowY:'auto', padding:16, paddingBottom: isMobile ? 72 : 16, overscrollBehavior:'contain' }}>
<BulkImportForm userPass={userPass} onCreated={load} />
</div>
)}
</div> </div>
{/* Mobile footer — fixed, hidden when any input is focused (keyboard open) */} {/* Mobile footer — fixed, hidden when any input is focused (keyboard open) */}