Family manager bug fixes
This commit is contained in:
@@ -211,7 +211,7 @@ export default function AddChildAliasModal({ onClose }) {
|
||||
<div className="text-sm font-medium" style={{ color: 'var(--text-secondary)', marginBottom: 10 }}>
|
||||
{editingAlias
|
||||
? `Editing: ${editingAlias.first_name} ${editingAlias.last_name}`
|
||||
: 'New Child Alias'}
|
||||
: 'Add Child'}
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
|
||||
@@ -789,11 +789,21 @@ function EventDetailModal({ event, onClose, onEdit, onAvailabilityChange, isTool
|
||||
const [noteSaving,setNoteSaving]=useState(false);
|
||||
const [avail,setAvail]=useState(event.availability||[]);
|
||||
const [expandedNotes,setExpandedNotes]=useState(new Set());
|
||||
// Guardian Only: responder select ('all' | 'self' | 'alias:<id>')
|
||||
// Guardian Only: responder select ('all' | 'self' | 'alias:<id>' | 'partner:<id>')
|
||||
const myAliases = event.my_aliases || [];
|
||||
const showResponderSelect = !!(event.has_players_group && myAliases.length > 0);
|
||||
const myPartner = event.my_partner || null;
|
||||
const showResponderSelect = !!(event.has_players_group && (myAliases.length > 0 || myPartner)) || !!(myPartner && event.in_guardians_group);
|
||||
const [responder, setResponder] = useState('all');
|
||||
|
||||
// Response that should be highlighted for the currently selected responder
|
||||
const activeResp = !showResponderSelect || responder === 'all'
|
||||
? myResp
|
||||
: responder === 'self'
|
||||
? myResp
|
||||
: responder.startsWith('alias:')
|
||||
? (avail.find(r => r.is_alias && r.alias_id === parseInt(responder.replace('alias:','')))?.response || null)
|
||||
: (avail.find(r => !r.is_alias && r.user_id === parseInt(responder.replace('partner:','')))?.response || null);
|
||||
|
||||
// Sync when parent reloads event after availability change
|
||||
useEffect(()=>{
|
||||
setMyResp(event.my_response);
|
||||
@@ -815,28 +825,30 @@ function EventDetailModal({ event, onClose, onEdit, onAvailabilityChange, isTool
|
||||
? [
|
||||
...(event.in_guardians_group ? [{ type:'self' }] : []),
|
||||
...myAliases.map(a => ({ type:'alias', aliasId:a.id })),
|
||||
...(myPartner ? [{ type:'partner', userId:myPartner.id }] : []),
|
||||
]
|
||||
: responder === 'self'
|
||||
? [{ type:'self' }]
|
||||
: [{ type:'alias', aliasId:parseInt(responder.replace('alias:','')) }];
|
||||
: responder.startsWith('alias:')
|
||||
? [{ type:'alias', aliasId:parseInt(responder.replace('alias:','')) }]
|
||||
: [{ type:'partner', userId:parseInt(responder.replace('partner:','')) }];
|
||||
|
||||
const getCurrentResp = (t) =>
|
||||
t.type === 'self' ? myResp
|
||||
: t.type === 'alias' ? (avail.find(r => r.is_alias && r.alias_id === t.aliasId)?.response || null)
|
||||
: (avail.find(r => !r.is_alias && r.user_id === t.userId)?.response || null);
|
||||
|
||||
// For "All": toggle all off only when every target already has this response;
|
||||
// otherwise set all to this response (avoids partial-toggle confusion)
|
||||
const allHaveResp = responder === 'all' && targets.every(t =>
|
||||
t.type === 'self'
|
||||
? myResp === resp
|
||||
: (avail.find(r => r.is_alias && r.alias_id === t.aliasId)?.response || null) === resp
|
||||
);
|
||||
const allHaveResp = responder === 'all' && targets.every(t => getCurrentResp(t) === resp);
|
||||
try {
|
||||
for (const t of targets) {
|
||||
const prevResp = t.type === 'self'
|
||||
? myResp
|
||||
: (avail.find(r => r.is_alias && r.alias_id === t.aliasId)?.response || null);
|
||||
const prevResp = getCurrentResp(t);
|
||||
const shouldDelete = responder === 'all' ? allHaveResp : prevResp === resp;
|
||||
if (shouldDelete) {
|
||||
await api.deleteAvailability(event.id, t.type === 'alias' ? t.aliasId : undefined);
|
||||
await api.deleteAvailability(event.id, t.type === 'alias' ? t.aliasId : undefined, t.type === 'partner' ? t.userId : undefined);
|
||||
} else {
|
||||
await api.setAvailability(event.id, resp, note, t.type === 'alias' ? t.aliasId : undefined);
|
||||
await api.setAvailability(event.id, resp, note, t.type === 'alias' ? t.aliasId : undefined, t.type === 'partner' ? t.userId : undefined);
|
||||
}
|
||||
}
|
||||
if (targets.some(t => t.type === 'self')) {
|
||||
@@ -979,8 +991,8 @@ function EventDetailModal({ event, onClose, onEdit, onAvailabilityChange, isTool
|
||||
<>
|
||||
<div style={{display:'flex',gap:8,marginBottom:12}}>
|
||||
{Object.entries(RESP_LABEL).map(([key,label])=>(
|
||||
<button key={key} onClick={()=>handleResp(key)} style={{flex:1,padding:'9px 4px',borderRadius:'var(--radius)',border:`2px solid ${RESP_COLOR[key]}`,background:myResp===key?RESP_COLOR[key]:'transparent',color:myResp===key?'white':RESP_COLOR[key],fontSize:13,fontWeight:600,cursor:'pointer',transition:'all 0.15s'}}>
|
||||
{myResp===key?'✓ ':''}{label}
|
||||
<button key={key} onClick={()=>handleResp(key)} style={{flex:1,padding:'9px 4px',borderRadius:'var(--radius)',border:`2px solid ${RESP_COLOR[key]}`,background:activeResp===key?RESP_COLOR[key]:'transparent',color:activeResp===key?'white':RESP_COLOR[key],fontSize:13,fontWeight:600,cursor:'pointer',transition:'all 0.15s'}}>
|
||||
{activeResp===key?'✓ ':''}{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -990,8 +1002,9 @@ function EventDetailModal({ event, onClose, onEdit, onAvailabilityChange, isTool
|
||||
<label style={{fontSize:11,color:'var(--text-tertiary)',textTransform:'uppercase',letterSpacing:'0.5px',display:'block',marginBottom:4}}>Responding for</label>
|
||||
<select value={responder} onChange={e=>setResponder(e.target.value)}
|
||||
style={{width:'100%',padding:'7px 10px',borderRadius:'var(--radius)',border:'1px solid var(--border)',background:'var(--surface)',color:'var(--text-primary)',fontSize:13}}>
|
||||
<option value="all">All</option>
|
||||
{event.in_guardians_group && <option value="self">{/* guardian's own name shown as self */}My own response</option>}
|
||||
<option value="all">Entire Family</option>
|
||||
{event.in_guardians_group && <option value="self">Myself</option>}
|
||||
{myPartner && <option value={`partner:${myPartner.id}`}>{myPartner.display_name || myPartner.name}</option>}
|
||||
{myAliases.map(a=><option key={a.id} value={`alias:${a.id}`}>{a.first_name} {a.last_name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -139,9 +139,9 @@ export const api = {
|
||||
createEvent: (body) => req('POST', '/schedule', body), // body may include recurrenceRule: {freq,interval,byDay,ends,endDate,endCount}
|
||||
updateEvent: (id, body) => req('PATCH', `/schedule/${id}`, body),
|
||||
deleteEvent: (id, scope = 'this', occurrenceStart = null) => req('DELETE', `/schedule/${id}`, { recurringScope: scope, occurrenceStart }),
|
||||
setAvailability: (id, response, note, aliasId) => req('PUT', `/schedule/${id}/availability`, { response, note, ...(aliasId ? { aliasId } : {}) }),
|
||||
setAvailability: (id, response, note, aliasId, forPartnerId) => req('PUT', `/schedule/${id}/availability`, { response, note, ...(aliasId ? { aliasId } : {}), ...(forPartnerId ? { forPartnerId } : {}) }),
|
||||
setAvailabilityNote: (id, note) => req('PATCH', `/schedule/${id}/availability/note`, { note }),
|
||||
deleteAvailability: (id, aliasId) => req('DELETE', `/schedule/${id}/availability${aliasId ? `?aliasId=${aliasId}` : ''}`),
|
||||
deleteAvailability: (id, aliasId, forPartnerId) => req('DELETE', `/schedule/${id}/availability${aliasId ? `?aliasId=${aliasId}` : forPartnerId ? `?forPartnerId=${forPartnerId}` : ''}`),
|
||||
getPendingAvailability: () => req('GET', '/schedule/me/pending'),
|
||||
bulkAvailability: (responses) => req('POST', '/schedule/me/bulk-availability', { responses }),
|
||||
importPreview: (file) => {
|
||||
|
||||
Reference in New Issue
Block a user