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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user