Files
fourge-portal/src/pages/team/Requests.jsx
T
2026-03-27 00:38:15 -04:00

163 lines
7.4 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';
export default function Requests() {
const [submissions, setSubmissions] = useState([]);
const [tasks, setTasks] = useState([]);
const [projects, setProjects] = useState([]);
const [companies, setCompanies] = useState([]);
const [loading, setLoading] = useState(true);
const [filterCompany, setFilterCompany] = useState('');
const [filterUser, setFilterUser] = useState('');
useEffect(() => {
async function load() {
const [{ data: subs }, { data: t }, { data: p }, { data: co }] = await Promise.all([
supabase.from('submissions').select('*').order('submitted_at', { ascending: false }),
supabase.from('tasks').select('*'),
supabase.from('projects').select('*'),
supabase.from('companies').select('id, name'),
]);
setSubmissions(subs || []);
setTasks(t || []);
setProjects(p || []);
setCompanies(co || []);
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">Requests Inbox</div>
<div className="page-subtitle">All incoming submissions initial requests and revisions.</div>
</div>
</div>
{companies.length > 0 && (
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 10 }}>
<button className={`btn btn-sm ${!filterCompany ? 'btn-primary' : 'btn-outline'}`} onClick={() => setFilterCompany('')}>All</button>
{companies.map(co => (
<button
key={co.id}
className={`btn btn-sm ${filterCompany === co.id ? 'btn-primary' : 'btn-outline'}`}
onClick={() => setFilterCompany(f => f === co.id ? '' : co.id)}
>
{co.name}
</button>
))}
</div>
)}
{[...new Set(submissions.map(s => s.submitted_by_name).filter(Boolean))].sort().length > 0 && (
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 20 }}>
<button className={`btn btn-sm ${!filterUser ? 'btn-primary' : 'btn-outline'}`} onClick={() => setFilterUser('')}>All Users</button>
{[...new Set(submissions.map(s => s.submitted_by_name).filter(Boolean))].sort().map(name => (
<button
key={name}
className={`btn btn-sm ${filterUser === name ? 'btn-primary' : 'btn-outline'}`}
onClick={() => setFilterUser(f => f === name ? '' : name)}
>
{name}
</button>
))}
</div>
)}
{submissions.length === 0 ? (
<div className="empty-state">
<h3>No requests yet</h3>
<p>Client requests will appear here.</p>
</div>
) : (() => {
// Group by task_id + version_number
const groupMap = {};
submissions.forEach(sub => {
const key = `${sub.task_id}-${sub.version_number}`;
if (!groupMap[key]) groupMap[key] = [];
groupMap[key].push(sub);
});
// Sort groups by latest submitted_at descending, then apply filters
const groups = Object.values(groupMap).filter(group => {
const primary = group.find(s => s.type !== 'amendment') || group[0];
const task = tasks.find(t => t.id === primary.task_id);
const project = projects.find(p => p.id === task?.project_id);
if (filterCompany && project?.company_id !== filterCompany) return false;
if (filterUser && !group.some(s => s.submitted_by_name === filterUser)) return false;
return true;
}).sort((a, b) => {
const aMax = Math.max(...a.map(s => new Date(s.submitted_at)));
const bMax = Math.max(...b.map(s => new Date(s.submitted_at)));
return bMax - aMax;
});
return groups.map(group => {
const primary = group.find(s => s.type !== 'amendment') || group[0];
const amendments = group.filter(s => s.type === 'amendment');
const task = tasks.find(t => t.id === primary.task_id);
const project = projects.find(p => p.id === task?.project_id);
const company = companies.find(co => co.id === project?.company_id);
return (
<div key={primary.id} className="request-card">
<div className="request-card-header">
<div>
<div className="request-card-title">
{primary.service_type}
<StatusBadge status={primary.type} />
</div>
<div className="request-card-meta">
From <strong>{primary.submitted_by_name}</strong>
{company && (
<> · <Link to={`/companies/${company.id}`} className="table-link">{company.name}</Link></>
)}
{' · '}{new Date(primary.submitted_at).toLocaleDateString()}
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<StatusBadge status={task?.status || 'not_started'} />
{task && <Link to={`/tasks/${task.id}`} className="btn btn-outline btn-sm">View Job</Link>}
</div>
</div>
<div style={{ display: 'flex', gap: 24, marginBottom: 12 }}>
<div>
<span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px', color: 'var(--text-muted)' }}>Deadline</span>
<div style={{ fontSize: 13, marginTop: 2 }}>{primary.deadline || 'Not specified'}</div>
</div>
<div>
<span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px', color: 'var(--text-muted)' }}>Project</span>
<div style={{ fontSize: 13, marginTop: 2 }}>
{project ? <Link to={`/projects/${project.id}`} className="table-link">{project.name}</Link> : '—'}
</div>
</div>
</div>
<p style={{ fontSize: 13, color: 'var(--text-secondary)', lineHeight: 1.6, marginBottom: amendments.length > 0 ? 12 : 0 }}>{primary.description}</p>
{amendments.map(amendment => (
<div key={amendment.id} style={{ padding: '12px 14px', background: 'var(--bg)', borderRadius: 8, border: '1px solid var(--border)', marginTop: 8 }}>
<div style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', color: 'var(--accent)', marginBottom: 6, letterSpacing: 0.5 }}>
Amended Request
</div>
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 6 }}>
{amendment.submitted_by_name} · {new Date(amendment.submitted_at).toLocaleDateString()}
</div>
<p style={{ fontSize: 13, lineHeight: 1.6, color: 'var(--text-secondary)', whiteSpace: 'pre-wrap', margin: 0 }}>{amendment.description}</p>
</div>
))}
</div>
);
});
})()}
</Layout>
);
}