Files
fourge-portal/src/pages/client/MyProjects.jsx
T
Krao Hasanee 2bf29f5699 Remove 'Add Request' button from each project card
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 11:38:27 -04:00

211 lines
8.2 KiB
React
Executable File

import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import Layout from '../../components/Layout';
import StatusBadge from '../../components/StatusBadge';
import { supabase } from '../../lib/supabase';
import { useAuth } from '../../context/AuthContext';
import { withTimeout } from '../../lib/withTimeout';
const rLabel = (v) => 'R' + String(v || 0).padStart(2, '0');
function ProjectGroup({ project, tasks, submissions, currentUserId, filter }) {
const [open, setOpen] = useState(true);
const filteredTasks = filter === 'mine'
? tasks.filter(task => {
const initial = submissions.find(s => s.task_id === task.id && s.type === 'initial');
return initial?.submitted_by === currentUserId;
})
: tasks;
if (filter === 'mine' && filteredTasks.length === 0) return null;
return (
<div className="interactive-surface" style={{ border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden', marginBottom: 8 }}>
{/* Project header — clickable to collapse */}
<button
className="interactive-panel-toggle"
onClick={() => setOpen(o => !o)}
style={{
width: '100%', display: 'flex', alignItems: 'center',
justifyContent: 'space-between', padding: '12px 16px',
background: 'var(--card-bg-2)', border: 'none', cursor: 'pointer',
borderBottom: open ? '1px solid var(--border)' : 'none',
fontFamily: 'inherit',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<Link
to={`/my-projects/${project.id}`}
onClick={e => e.stopPropagation()}
style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-primary)', textDecoration: 'none' }}
>
{project.name}
</Link>
<span style={{ fontSize: 11, fontWeight: 600, padding: '2px 8px', borderRadius: 4, background: 'rgba(245,165,35,0.15)', color: 'var(--accent)' }}>
{filteredTasks.length} request{filteredTasks.length !== 1 ? 's' : ''}
</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<StatusBadge status={project.status} />
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{open ? '▲' : '▼'}</span>
</div>
</button>
{open && (
<div style={{ background: 'var(--card-bg)' }}>
{filteredTasks.length === 0 ? (
<div style={{ padding: '16px', fontSize: 13, color: 'var(--text-muted)', textAlign: 'center' }}>
No requests in this project yet.
</div>
) : (
filteredTasks.map((task, i) => {
const taskSubs = submissions.filter(s => s.task_id === task.id);
const initialSub = taskSubs.find(s => s.type === 'initial') || taskSubs[0];
const latestSub = taskSubs[taskSubs.length - 1];
const hasRevision = latestSub && initialSub && latestSub.id !== initialSub.id && latestSub.submitted_by_name !== initialSub.submitted_by_name;
const isMine = initialSub?.submitted_by === currentUserId;
return (
<Link
key={task.id}
to={`/my-requests/${task.id}`}
className="interactive-row"
style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '12px 16px',
borderBottom: i < filteredTasks.length - 1 ? '1px solid var(--border)' : 'none',
gap: 8, textDecoration: 'none', cursor: 'pointer',
}}
>
<div style={{ minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<span style={{ fontWeight: 600, fontSize: 13, color: 'var(--text-primary)' }}>
{task.title}
</span>
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>
{rLabel(task.current_version)}
</span>
{isMine && (
<span style={{ fontSize: 11, background: 'rgba(245,165,35,0.15)', color: 'var(--accent)', padding: '1px 7px', borderRadius: 4, fontWeight: 600 }}>
Mine
</span>
)}
</div>
<div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 3 }}>
{initialSub?.submitted_by_name && <>By {initialSub.submitted_by_name}</>}
{hasRevision && <> · Updated by {latestSub.submitted_by_name}</>}
</div>
</div>
<StatusBadge status={task.status} />
</Link>
);
})
)}
</div>
)}
</div>
);
}
export default function MyProjects() {
const { currentUser } = useAuth();
const [projects, setProjects] = useState([]);
const [tasks, setTasks] = useState([]);
const [submissions, setSubmissions] = useState([]);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState('all'); // 'all' | 'mine'
useEffect(() => {
async function load() {
try {
const [{ data: p }, { data: t }] = await withTimeout(Promise.all([
supabase.from('projects').select('*').order('created_at', { ascending: false }),
supabase.from('tasks').select('*').order('submitted_at', { ascending: false }),
]), 12000, 'Projects load');
setProjects(p || []);
setTasks(t || []);
if (t && t.length > 0) {
const { data: subs } = await withTimeout(
supabase
.from('submissions')
.select('id, task_id, submitted_by, submitted_by_name, version_number, type')
.in('task_id', t.map(task => task.id))
.order('version_number'),
12000,
'Project submissions load'
);
setSubmissions(subs || []);
} else {
setSubmissions([]);
}
} catch (error) {
console.error('MyProjects load failed:', error);
setProjects([]);
setTasks([]);
setSubmissions([]);
} finally {
setLoading(false);
}
}
load();
}, []);
if (loading) return <Layout><p style={{ padding: 24, color: 'var(--text-muted)' }}>Loading...</p></Layout>;
return (
<Layout>
<div className="page-header">
<div>
<div className="page-title">Projects</div>
<div className="page-subtitle">All work for your company.</div>
</div>
<Link to="/new-project" className="btn btn-primary">+ New Project</Link>
</div>
<div className="card page-toolbar">
<div className="page-toolbar-grid">
<div className="page-toolbar-section">
<div className="card-title" style={{ marginBottom: 10 }}>Filter</div>
<div className="page-toolbar-filters">
<button
className={`btn btn-sm ${filter === 'all' ? 'btn-primary' : 'btn-outline'}`}
onClick={() => setFilter('all')}
>
All Requests
</button>
<button
className={`btn btn-sm ${filter === 'mine' ? 'btn-primary' : 'btn-outline'}`}
onClick={() => setFilter('mine')}
>
Mine Only
</button>
</div>
</div>
</div>
</div>
{projects.length === 0 ? (
<div className="empty-state">
<h3>No projects yet</h3>
<p>Submit a request and a project will be created automatically.</p>
<Link to="/new-project" className="btn btn-primary" style={{ marginTop: 16 }}>+ New Project</Link>
</div>
) : (
projects.map(project => (
<ProjectGroup
key={project.id}
project={project}
tasks={tasks.filter(t => t.project_id === project.id)}
submissions={submissions}
currentUserId={currentUser.id}
filter={filter}
/>
))
)}
</Layout>
);
}