feat: project detail overhaul, realtime updates, avatar RLS fix

- ProjectDetail: full redesign — icon meta row, completion progress bar, main contact card, activity feed, subcontractor management modal with multi-select
- ProjectDetail: task table matches Tasks.jsx (R#, assigned avatar, priority, type, deadline, status) with status tabs + counts
- Realtime: useRefetchOnFocus + useRealtimeSubscription hooks wired to Tasks, TaskDetail, ProjectDetail, TeamDashboard
- AuthContext: live profile updates via Supabase realtime channel
- Tasks/ProjectDetail: assignee avatar join added for all roles (client, external, team)
- RLS: allow all authenticated users to read profiles (fixes avatar display across roles)
- RequestForm: lockedFields prop for pre-filled read-only company/project fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-31 16:55:58 -04:00
parent 35d92d2176
commit 70ad8d0cef
8 changed files with 546 additions and 203 deletions
+13 -18
View File
@@ -41,6 +41,7 @@ export default function RequestForm({
submitLabel = 'Submit Request',
initialCompanyId = '',
initialValues = null,
lockedFields = [],
}) {
const [form, setForm] = useState(() => ({ ...emptyForm(initialCompanyId), ...(initialValues || {}) }));
const prevCompanyIdRef = useRef(initialValues?.companyId || initialCompanyId || null);
@@ -164,33 +165,27 @@ export default function RequestForm({
<form onSubmit={handleSubmit}>
{/* Row 1: Company + Project */}
<div className="grid-2">
{showCompanySelect ? (
{lockedFields.includes('company') ? (
<div className="form-group">
<label style={modalLabelStyle}>Company</label>
<input value={companies.find(c => c.id === companyId)?.name || companyId} disabled style={{ ...modalInputStyle, opacity: 0.5, cursor: 'not-allowed' }} />
</div>
) : showCompanySelect ? (
<div className="form-group">
<label style={modalLabelStyle}>Company *</label>
<select
value={form.companyId}
onChange={e => setForm(f => ({ ...f, companyId: e.target.value }))}
required
style={modalInputStyle}
>
<select value={form.companyId} onChange={e => setForm(f => ({ ...f, companyId: e.target.value }))} required style={modalInputStyle}>
<option value="">Select company...</option>
{companies.map(co => <option key={co.id} value={co.id}>{co.name}</option>)}
</select>
</div>
) : <div />}
<div className="form-group">
<label style={modalLabelStyle}>Project *</label>
{isTypingProject ? (
<label style={modalLabelStyle}>Project {!lockedFields.includes('project') && '*'}</label>
{lockedFields.includes('project') ? (
<input value={form.project} disabled style={{ ...modalInputStyle, opacity: 0.5, cursor: 'not-allowed' }} />
) : isTypingProject ? (
<div style={{ display: 'flex', gap: 8 }}>
<input
type="text"
placeholder="Enter project name..."
value={newProjectName}
onChange={e => setNewProjectName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAddProject(); } }}
autoFocus
style={{ ...modalInputStyle, flex: 1 }}
/>
<input type="text" placeholder="Enter project name..." value={newProjectName} onChange={e => setNewProjectName(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAddProject(); } }} autoFocus style={{ ...modalInputStyle, flex: 1 }} />
<button type="button" className="btn btn-outline" onClick={handleAddProject} disabled={!newProjectName.trim()}>Add</button>
<button type="button" className="btn btn-outline" onClick={() => { setIsTypingProject(false); setNewProjectName(''); }}>Cancel</button>
</div>