Add Projects page for team members with search, nav under Requests
This commit is contained in:
@@ -12,6 +12,7 @@ const Dashboard = lazy(() => import('./pages/team/Dashboard'));
|
|||||||
const Companies = lazy(() => import('./pages/team/Companies'));
|
const Companies = lazy(() => import('./pages/team/Companies'));
|
||||||
const CompanyDetail = lazy(() => import('./pages/team/CompanyDetail'));
|
const CompanyDetail = lazy(() => import('./pages/team/CompanyDetail'));
|
||||||
const ProjectDetail = lazy(() => import('./pages/team/ProjectDetail'));
|
const ProjectDetail = lazy(() => import('./pages/team/ProjectDetail'));
|
||||||
|
const TeamProjects = lazy(() => import('./pages/team/TeamProjects'));
|
||||||
const Requests = lazy(() => import('./pages/team/Requests'));
|
const Requests = lazy(() => import('./pages/team/Requests'));
|
||||||
const Invoices = lazy(() => import('./pages/team/Invoices'));
|
const Invoices = lazy(() => import('./pages/team/Invoices'));
|
||||||
const MeetingNotes = lazy(() => import('./pages/team/MeetingNotes'));
|
const MeetingNotes = lazy(() => import('./pages/team/MeetingNotes'));
|
||||||
@@ -56,6 +57,7 @@ export default function App() {
|
|||||||
<Route path="/companies" element={<ProtectedRoute role="team"><Companies /></ProtectedRoute>} />
|
<Route path="/companies" element={<ProtectedRoute role="team"><Companies /></ProtectedRoute>} />
|
||||||
<Route path="/companies/:id" element={<ProtectedRoute role="team"><CompanyDetail /></ProtectedRoute>} />
|
<Route path="/companies/:id" element={<ProtectedRoute role="team"><CompanyDetail /></ProtectedRoute>} />
|
||||||
<Route path="/requests" element={<ProtectedRoute role="team"><Requests /></ProtectedRoute>} />
|
<Route path="/requests" element={<ProtectedRoute role="team"><Requests /></ProtectedRoute>} />
|
||||||
|
<Route path="/team-projects" element={<ProtectedRoute role="team"><TeamProjects /></ProtectedRoute>} />
|
||||||
<Route path="/meeting-notes" element={<ProtectedRoute role="team"><MeetingNotes /></ProtectedRoute>} />
|
<Route path="/meeting-notes" element={<ProtectedRoute role="team"><MeetingNotes /></ProtectedRoute>} />
|
||||||
<Route path="/invoices" element={<ProtectedRoute role="team"><Invoices /></ProtectedRoute>} />
|
<Route path="/invoices" element={<ProtectedRoute role="team"><Invoices /></ProtectedRoute>} />
|
||||||
<Route path="/invoices/new" element={<ProtectedRoute role="team"><CreateInvoice /></ProtectedRoute>} />
|
<Route path="/invoices/new" element={<ProtectedRoute role="team"><CreateInvoice /></ProtectedRoute>} />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ function TeamNav({ onNav }) {
|
|||||||
const primaryLinks = [
|
const primaryLinks = [
|
||||||
{ to: '/dashboard', label: 'Dashboard' },
|
{ to: '/dashboard', label: 'Dashboard' },
|
||||||
{ to: '/requests', label: 'Requests' },
|
{ to: '/requests', label: 'Requests' },
|
||||||
|
{ to: '/team-projects', label: 'Projects' },
|
||||||
{ to: '/file-sharing', label: 'File Sharing' },
|
{ to: '/file-sharing', label: 'File Sharing' },
|
||||||
{ to: '/companies', label: 'Clients & Users' },
|
{ to: '/companies', label: 'Clients & Users' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import Layout from '../../components/Layout';
|
||||||
|
import StatusBadge from '../../components/StatusBadge';
|
||||||
|
import { supabase } from '../../lib/supabase';
|
||||||
|
|
||||||
|
export default function TeamProjects() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [projects, setProjects] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
supabase
|
||||||
|
.from('projects')
|
||||||
|
.select('id, name, status, created_at, company:companies(id, name)')
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.then(({ data }) => {
|
||||||
|
setProjects(data || []);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const filtered = projects.filter(p =>
|
||||||
|
!search ||
|
||||||
|
p.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
|
p.company?.name?.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<div className="page-header">
|
||||||
|
<div>
|
||||||
|
<div className="page-title">Projects</div>
|
||||||
|
<div className="page-subtitle">All active client projects.</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search projects or clients..."
|
||||||
|
value={search}
|
||||||
|
onChange={e => setSearch(e.target.value)}
|
||||||
|
style={{ width: 240 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<p style={{ padding: 24, color: 'var(--text-muted)' }}>Loading...</p>
|
||||||
|
) : filtered.length === 0 ? (
|
||||||
|
<div className="empty-state">
|
||||||
|
<h3>No projects found</h3>
|
||||||
|
<p>Projects are created from the Clients & Users page.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="table-wrapper">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Project</th>
|
||||||
|
<th>Client</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Started</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{filtered.map(p => (
|
||||||
|
<tr key={p.id} style={{ cursor: 'pointer' }} onClick={() => navigate(`/projects/${p.id}`)}>
|
||||||
|
<td style={{ fontWeight: 600 }}>{p.name}</td>
|
||||||
|
<td style={{ color: 'var(--text-muted)' }}>{p.company?.name || '—'}</td>
|
||||||
|
<td><StatusBadge status={p.status} /></td>
|
||||||
|
<td style={{ color: 'var(--text-muted)' }}>{new Date(p.created_at).toLocaleDateString()}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user