v0.12.42 new availibilty list download

This commit is contained in:
2026-03-30 08:39:55 -04:00
parent 6a2f4438f9
commit e8e941c436
5 changed files with 79 additions and 10 deletions

View File

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

View File

@@ -823,6 +823,57 @@ function EventDetailModal({ event, onClose, onEdit, onAvailabilityChange, isTool
const toggleNote=id=>setExpandedNotes(prev=>{const s=new Set(prev);s.has(id)?s.delete(id):s.add(id);return s;});
const handleDownloadAvailability = () => {
// Format as "Lastname, Firstname" using first_name/last_name fields when available
const fmtName = u => {
const last = (u.last_name || '').trim();
const first = (u.first_name || '').trim();
if (last && first) return `${last}, ${first}`;
// Fall back to splitting the combined name field
const parts = (u.name || u.display_name || 'Unknown').trim().split(/\s+/);
if (parts.length >= 2) return `${parts[parts.length - 1]}, ${parts.slice(0, -1).join(' ')}`;
return parts[0] || 'Unknown';
};
const sortByLastName = arr => [...arr].sort((a, b) => fmtName(a).localeCompare(fmtName(b)));
const fmtEntry = u => {
const note = (u.note || '').trim();
return note ? `${fmtName(u)} - Note: ${note}` : fmtName(u);
};
const going = sortByLastName(avail.filter(r => r.response === 'going'));
const maybe = sortByLastName(avail.filter(r => r.response === 'maybe'));
const notGoing = sortByLastName(avail.filter(r => r.response === 'not_going'));
const noResp = sortByLastName(event.no_response_users || []);
const sections = [
{ heading: 'Going', rows: going },
{ heading: 'Maybe', rows: maybe },
{ heading: 'Not Going', rows: notGoing },
{ heading: 'No Response', rows: noResp },
];
const eventDate = event.start_at ? fmtDate(new Date(event.start_at)) : '';
const lines = [`${event.title}${eventDate ? ' — ' + eventDate : ''}`, ''];
for (const sec of sections) {
lines.push(`#### ${sec.heading}`);
if (sec.rows.length === 0) {
lines.push('(none)');
} else {
sec.rows.forEach(r => lines.push(fmtEntry(r)));
}
lines.push('');
}
const blob = new Blob([lines.join('\n')], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const safeName = (event.title || 'event').replace(/[^a-z0-9]+/gi, '_').toLowerCase();
a.href = url;
a.download = `availability_${safeName}.txt`;
a.click();
URL.revokeObjectURL(url);
};
return ReactDOM.createPortal(
<div className="modal-overlay" onClick={e=>e.target===e.currentTarget&&onClose()}>
<div className="modal" style={{maxWidth:520,maxHeight:'88vh',overflowY:'auto'}}>
@@ -861,7 +912,20 @@ function EventDetailModal({ event, onClose, onEdit, onAvailabilityChange, isTool
{!!event.track_availability&&(
<div style={{borderTop:'1px solid var(--border)',paddingTop:16,marginTop:4}}>
<div style={{fontSize:12,fontWeight:700,color:'var(--text-tertiary)',textTransform:'uppercase',letterSpacing:'0.6px',marginBottom:10}}>Your Availability</div>
<div style={{display:'flex',alignItems:'center',marginBottom:10}}>
<div style={{fontSize:12,fontWeight:700,color:'var(--text-tertiary)',textTransform:'uppercase',letterSpacing:'0.6px',flex:1}}>Your Availability</div>
{isToolManager&&(
<button
onClick={handleDownloadAvailability}
title="Download Availability List"
style={{background:'none',border:'none',padding:2,cursor:'pointer',color:'var(--text-secondary)',display:'flex',alignItems:'center',borderRadius:4}}
>
<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="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m.75 12 3 3m0 0 3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
</svg>
</button>
)}
</div>
{isPast ? (
<p style={{fontSize:13,color:'var(--text-tertiary)',marginBottom:16}}>Past event availability is read-only.</p>
) : (