feat: review popup drag-drop attachments, .md styling
This commit is contained in:
+26
-22
@@ -223,6 +223,8 @@ export default function TaskDetail() {
|
||||
const [reviewFiles, setReviewFiles] = useState([]);
|
||||
const [reviewNotes, setReviewNotes] = useState('');
|
||||
const [reviewSaving, setReviewSaving] = useState(false);
|
||||
const [reviewDragging, setReviewDragging] = useState(false);
|
||||
const reviewDragCounter = useRef(0);
|
||||
const reviewFileRef = useRef(null);
|
||||
const [comments, setComments] = useState([]);
|
||||
const [commentBody, setCommentBody] = useState('');
|
||||
@@ -771,42 +773,44 @@ export default function TaskDetail() {
|
||||
</div>
|
||||
|
||||
{reviewModal && (
|
||||
<div style={popupOverlayStyle} onClick={() => { if (!reviewSaving) { setReviewModal(false); setReviewFiles([]); } }}>
|
||||
<div style={{ ...popupSurfaceStyle, background: 'var(--card-bg)', width: 'min(480px, 96vw)', display: 'flex', flexDirection: 'column', gap: 14 }} onClick={e => e.stopPropagation()}>
|
||||
<div style={popupOverlayStyle} onClick={() => { if (!reviewSaving) { setReviewModal(false); setReviewFiles([]); setReviewNotes(''); } }}>
|
||||
<div style={{ ...popupSurfaceStyle, background: 'var(--card-bg)', width: 'min(480px, 96vw)', maxHeight: 'calc(100vh - 48px)', overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 14 }} onClick={e => e.stopPropagation()}>
|
||||
<div style={{ fontSize: 11, fontWeight: 500, textTransform: 'uppercase', letterSpacing: 0.8, color: 'var(--text-secondary)' }}>Place in Review</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 11, fontWeight: 500, textTransform: 'uppercase', letterSpacing: 0.8, color: 'var(--text-secondary)', marginBottom: 6 }}>Notes <span style={{ color: 'var(--text-muted)', fontWeight: 400, textTransform: 'none', letterSpacing: 0 }}>(optional)</span></div>
|
||||
<textarea
|
||||
rows={4}
|
||||
placeholder="Add notes for this submission…"
|
||||
value={reviewNotes}
|
||||
onChange={e => setReviewNotes(e.target.value)}
|
||||
style={{ fontSize: 13, color: 'var(--text-primary)', background: 'var(--card-bg-2)', border: '1px solid var(--border)', borderRadius: 6, padding: '6px 10px', width: '100%', fontFamily: 'inherit', boxSizing: 'border-box', resize: 'vertical' }}
|
||||
/>
|
||||
<textarea rows={4} placeholder="Add notes for this submission…" value={reviewNotes} onChange={e => setReviewNotes(e.target.value)} style={{ fontSize: 13, color: 'var(--text-primary)', background: 'var(--card-bg-2)', border: '1px solid var(--border)', borderRadius: 6, padding: '6px 10px', width: '100%', fontFamily: 'inherit', boxSizing: 'border-box', resize: 'vertical' }} />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 11, fontWeight: 500, textTransform: 'uppercase', letterSpacing: 0.8, color: 'var(--text-secondary)', marginBottom: 6 }}>Attachments <span style={{ color: 'var(--text-muted)', fontWeight: 400, textTransform: 'none', letterSpacing: 0 }}>(optional)</span></div>
|
||||
<input
|
||||
ref={reviewFileRef}
|
||||
type="file"
|
||||
multiple
|
||||
style={{ fontSize: 13, color: 'var(--text-primary)', background: 'var(--card-bg-2)', border: '1px solid var(--border)', borderRadius: 6, padding: '6px 10px', width: '100%', fontFamily: 'inherit', boxSizing: 'border-box' }}
|
||||
onChange={e => setReviewFiles(Array.from(e.target.files || []))}
|
||||
/>
|
||||
<input ref={reviewFileRef} type="file" multiple id="review-file-input" style={{ display: 'none' }} onChange={e => { setReviewFiles(prev => [...prev, ...Array.from(e.target.files)]); e.target.value = ''; }} />
|
||||
<div
|
||||
onDragEnter={e => { e.preventDefault(); reviewDragCounter.current++; setReviewDragging(true); }}
|
||||
onDragLeave={e => { e.preventDefault(); reviewDragCounter.current--; if (reviewDragCounter.current === 0) setReviewDragging(false); }}
|
||||
onDragOver={e => e.preventDefault()}
|
||||
onDrop={e => { e.preventDefault(); reviewDragCounter.current = 0; setReviewDragging(false); setReviewFiles(prev => [...prev, ...Array.from(e.dataTransfer.files)]); }}
|
||||
onClick={() => reviewFileRef.current?.click()}
|
||||
style={{ border: `2px dashed ${reviewDragging ? 'var(--accent)' : reviewFiles.length > 0 ? 'var(--accent)' : 'var(--border)'}`, borderRadius: 8, padding: '16px', textAlign: 'center', background: reviewDragging ? 'color-mix(in srgb, var(--accent) 8%, var(--bg))' : 'transparent', transition: 'all 160ms', cursor: 'pointer' }}
|
||||
>
|
||||
<div style={{ fontSize: 20, marginBottom: 4 }}>{reviewDragging ? '📂' : '📎'}</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--text-primary)' }}>
|
||||
{reviewDragging ? 'Drop files here' : reviewFiles.length > 0 ? `${reviewFiles.length} file${reviewFiles.length !== 1 ? 's' : ''} attached — click or drag to add more` : 'Click or drag files here'}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 2 }}>Any file type accepted</div>
|
||||
</div>
|
||||
{reviewFiles.length > 0 && (
|
||||
<div style={{ marginTop: 6, display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
<div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{reviewFiles.map((f, i) => (
|
||||
<div key={i} style={{ fontSize: 12, color: 'var(--text-muted)', display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span>{f.name}</span>
|
||||
<button type="button" className="btn-icon btn-icon-danger" style={{ fontSize: 10 }} onClick={() => setReviewFiles(prev => prev.filter((_, j) => j !== i))}>✕</button>
|
||||
<div key={i} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '5px 10px', background: 'var(--card-bg-2)', borderRadius: 6, border: '1px solid var(--border)' }}>
|
||||
<span style={{ fontSize: 12, color: 'var(--text-primary)' }}>{f.name}</span>
|
||||
<button type="button" className="btn btn-danger" onClick={() => setReviewFiles(prev => prev.filter((_, j) => j !== i))}>Remove</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', paddingTop: 4 }}>
|
||||
<button className="btn btn-primary" disabled={reviewSaving} onClick={handleConfirmReview}>{reviewSaving ? 'Submitting…' : 'Place in Review'}</button>
|
||||
<button className="btn btn-outline" disabled={reviewSaving} onClick={() => { setReviewModal(false); setReviewFiles([]); }}>Cancel</button>
|
||||
<button className="btn btn-outline" disabled={reviewSaving} onClick={handleConfirmReview}>{reviewSaving ? 'Submitting…' : 'Place in Review'}</button>
|
||||
<button className="btn btn-outline" disabled={reviewSaving} onClick={() => { setReviewModal(false); setReviewFiles([]); setReviewNotes(''); }}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user