v0.9.69 updated UI layout and styles
This commit is contained in:
@@ -189,6 +189,11 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
|
||||
const def = selectedDate ? selectedDate.toISOString().slice(0,10) : new Date().toISOString().slice(0,10);
|
||||
const [title, setTitle] = useState(event?.title||'');
|
||||
const [typeId, setTypeId] = useState(event?.event_type_id ? String(event.event_type_id) : '');
|
||||
const [localTypes, setLocalTypes] = useState(eventTypes);
|
||||
const [showAddType, setShowAddType] = useState(false);
|
||||
const [newTypeName, setNewTypeName] = useState('');
|
||||
const [newTypeColour, setNewTypeColour] = useState('#6366f1');
|
||||
const [savingType, setSavingType] = useState(false);
|
||||
const [sd, setSd] = useState(event ? toDateIn(event.start_at) : def);
|
||||
const [st, setSt] = useState(event ? toTimeIn(event.start_at) : '09:00');
|
||||
const [ed, setEd] = useState(event ? toDateIn(event.end_at) : def);
|
||||
@@ -208,21 +213,34 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
|
||||
const [showRecurrence, setShowRecurrence] = useState(false);
|
||||
const [showGroups, setShowGroups] = useState(false);
|
||||
|
||||
// Auto-set typeId to default event type
|
||||
// Sync and initialise typeId
|
||||
useEffect(() => {
|
||||
setLocalTypes(eventTypes);
|
||||
if(!event && typeId==='' && eventTypes.length>0) {
|
||||
const def = eventTypes.find(t=>t.is_default) || eventTypes[0];
|
||||
if(def) setTypeId(String(def.id));
|
||||
}
|
||||
}, [eventTypes]);
|
||||
|
||||
const createEventType = async () => {
|
||||
if(!newTypeName.trim()) return;
|
||||
setSavingType(true);
|
||||
try {
|
||||
const r = await api.createEventType({ name: newTypeName.trim(), colour: newTypeColour });
|
||||
setLocalTypes(prev => [...prev, r.eventType]);
|
||||
setTypeId(String(r.eventType.id));
|
||||
setNewTypeName(''); setShowAddType(false);
|
||||
} catch(e) { toast(e.message, 'error'); }
|
||||
finally { setSavingType(false); }
|
||||
};
|
||||
|
||||
// When start date changes, match end date
|
||||
useEffect(() => { if(!event) setEd(sd); }, [sd]);
|
||||
|
||||
// When type or start time changes, auto-set end time
|
||||
useEffect(() => {
|
||||
if(!sd||!st) return;
|
||||
const typ = eventTypes.find(t=>t.id===Number(typeId));
|
||||
const typ = localTypes.find(t=>t.id===Number(typeId));
|
||||
const dur = typ?.default_duration_hrs||1;
|
||||
const start = buildISO(sd,st);
|
||||
if(start && !event) { setEd(toDateIn(addHours(start,dur))); setEt(toTimeIn(addHours(start,dur))); }
|
||||
@@ -262,8 +280,28 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
|
||||
|
||||
{/* Event Type */}
|
||||
<MobileRow icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M22 2H2l8 9.46V19l4 2v-8.54L22 2z"/></svg>} label="Event Type">
|
||||
<select value={typeId} onChange={e=>setTypeId(e.target.value)} style={{ background:'transparent',border:'none',fontSize:15,color:'var(--text-primary)',width:'100%',outline:'none' }}>
|
||||
{eventTypes.map(t=><option key={t.id} value={t.id}>{t.name}</option>)}
|
||||
<div>
|
||||
<div style={{ display:'flex', alignItems:'center', gap:8 }}>
|
||||
<select value={typeId} onChange={e=>setTypeId(e.target.value)} style={{ background:'transparent',border:'none',fontSize:15,color:'var(--text-primary)',flex:1,outline:'none' }}>
|
||||
{localTypes.map(t=><option key={t.id} value={t.id}>{t.name}</option>)}
|
||||
</select>
|
||||
{isToolManager && (
|
||||
<button onClick={()=>setShowAddType(v=>!v)} style={{ background:'none',border:'none',cursor:'pointer',color:'var(--primary)',fontSize:13,fontWeight:600,flexShrink:0,padding:'2px 4px' }}>
|
||||
{showAddType ? 'Cancel' : '+ Type'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{showAddType && (
|
||||
<div style={{ marginTop:10, padding:'12px', background:'var(--background)', borderRadius:'var(--radius)', border:'1px solid var(--border)' }}>
|
||||
<input value={newTypeName} onChange={e=>setNewTypeName(e.target.value)} placeholder="Type name…" style={{ width:'100%', padding:'8px 10px', border:'1px solid var(--border)', borderRadius:'var(--radius)', fontSize:14, marginBottom:8, boxSizing:'border-box', background:'var(--surface)', color:'var(--text-primary)' }}/>
|
||||
<div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:8 }}>
|
||||
<label style={{ fontSize:13, color:'var(--text-tertiary)', flexShrink:0 }}>Colour</label>
|
||||
<input type="color" value={newTypeColour} onChange={e=>setNewTypeColour(e.target.value)} style={{ flex:1, height:32, border:'1px solid var(--border)', borderRadius:4, padding:2 }}/>
|
||||
</div>
|
||||
<button onClick={createEventType} disabled={savingType||!newTypeName.trim()} style={{ width:'100%', padding:'9px', background:'var(--primary)', color:'white', border:'none', borderRadius:'var(--radius)', fontSize:14, fontWeight:600, cursor:'pointer', opacity:savingType?0.6:1 }}>{savingType?'Creating…':'Create Type'}</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</select>
|
||||
</MobileRow>
|
||||
|
||||
|
||||
@@ -1003,7 +1003,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
{createOpen && (
|
||||
<div style={{ position:'absolute', top:'100%', left:0, right:0, zIndex:100, background:'var(--surface)', border:'1px solid var(--border)', borderRadius:'var(--radius)', marginTop:4, boxShadow:'0 4px 16px rgba(0,0,0,0.12)' }}>
|
||||
<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);}],
|
||||
['Bulk Event Import', ()=>{setPanel('bulkImport');setCreateOpen(false);}]
|
||||
@@ -1063,11 +1063,7 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
||||
<div style={{ display:'flex', alignItems:'center', gap:8, padding:'8px 16px', borderBottom:'1px solid var(--border)', background:'var(--surface)', flexShrink:0, flexWrap:'nowrap' }}>
|
||||
{/* Mobile title + create */}
|
||||
{isMobile && (
|
||||
<>
|
||||
<span style={{ fontSize:15, fontWeight:700, flex:1 }}>Team Schedule</span>
|
||||
{/* User footer trigger on mobile — compact avatar button */}
|
||||
<UserFooter onProfile={onProfile} onHelp={onHelp} onAbout={onAbout} mobileCompact={true} />
|
||||
</>
|
||||
<span style={{ fontSize:15, fontWeight:700, flex:1 }}>Team Schedule</span>
|
||||
)}
|
||||
|
||||
{!isMobile && (
|
||||
@@ -1172,13 +1168,31 @@ export default function SchedulePage({ isToolManager, isMobile, onProfile, onHel
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile FAB for creating events */}
|
||||
{isMobile && isToolManager && panel === 'calendar' && (
|
||||
<button onClick={()=>{setPanel('eventForm');setEditingEvent(null);}} style={{ position:'fixed',bottom:24,right:24,zIndex:30,width:56,height:56,borderRadius:'50%',background:'var(--primary)',color:'white',border:'none',cursor:'pointer',boxShadow:'0 4px 16px rgba(0,0,0,0.25)',display:'flex',alignItems:'center',justifyContent:'center' }}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor" width="24" height="24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
</button>
|
||||
{/* Mobile bottom bar: UserFooter + Create Event button */}
|
||||
{isMobile && (
|
||||
<div style={{ background:'var(--surface)', borderTop:'1px solid var(--border)', flexShrink:0 }}>
|
||||
{isToolManager && panel === 'calendar' && (
|
||||
<div style={{ padding:'10px 16px 4px', position:'relative' }} ref={createRef}>
|
||||
<button className="newchat-btn" onClick={() => setCreateOpen(v=>!v)} style={{ width:'100%', justifyContent:'center', gap:8 }}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" width="18" height="18">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
Create Event
|
||||
</button>
|
||||
{createOpen && (
|
||||
<div style={{ position:'absolute', bottom:'100%', left:16, right:16, zIndex:100, background:'var(--surface-variant)', border:'1px solid var(--border)', borderRadius:'var(--radius)', marginBottom:4, boxShadow:'0 -4px 16px rgba(0,0,0,0.15)' }}>
|
||||
{[['Event', ()=>{setPanel('eventForm');setEditingEvent(null);setCreateOpen(false);}],
|
||||
['Event Type', ()=>{setPanel('eventTypes');setCreateOpen(false);}],
|
||||
].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>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<UserFooter onProfile={onProfile} onHelp={onHelp} onAbout={onAbout} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Event detail modal */}
|
||||
|
||||
Reference in New Issue
Block a user