Projects: collapse by default + company tabs

- ProjectGroup starts collapsed (open=false)
- Multi-company clients see tab switcher matching Invoices page style
- Single-company clients: no tabs, no change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-13 12:05:03 -04:00
parent 2a9c743823
commit a519e806e5
+41 -39
View File
@@ -9,7 +9,7 @@ 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 [open, setOpen] = useState(false);
const filteredTasks = filter === 'mine'
? tasks.filter(task => {
@@ -115,6 +115,8 @@ export default function MyProjects() {
const [submissions, setSubmissions] = useState([]);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState('all'); // 'all' | 'mine'
const companies = currentUser.companies?.length ? currentUser.companies : (currentUser.company ? [currentUser.company] : []);
const [activeCompanyId, setActiveCompanyId] = useState(() => companies[0]?.id || null);
useEffect(() => {
async function load() {
@@ -187,6 +189,27 @@ export default function MyProjects() {
</div>
</div>
{companies.length > 1 && (
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }} className="card-title">
{companies.map((company, index) => (
<span key={company.id} style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
{index > 0 && <span style={{ color: 'var(--text-muted)' }}>|</span>}
<button
type="button"
onClick={() => setActiveCompanyId(company.id)}
style={{
background: 'none', border: 'none', padding: 0, margin: 0,
cursor: 'pointer', font: 'inherit', textTransform: 'inherit', letterSpacing: 'inherit',
color: activeCompanyId === company.id ? 'var(--text-primary)' : 'var(--text-muted)',
}}
>
{company.name}
</button>
</span>
))}
</div>
)}
{projects.length === 0 ? (
<div className="empty-state">
<h3>No projects yet</h3>
@@ -194,47 +217,26 @@ export default function MyProjects() {
<Link to="/new-project" className="btn btn-primary" style={{ marginTop: 16 }}>+ New Project</Link>
</div>
) : (() => {
const companies = currentUser.companies || (currentUser.company ? [currentUser.company] : []);
const multiCompany = companies.length > 1;
const companyMap = Object.fromEntries(companies.map(c => [c.id, c.name]));
const grouped = companies
.map(c => ({ company: c, projects: projects.filter(p => p.company_id === c.id) }))
.filter(g => g.projects.length > 0);
const ungrouped = projects.filter(p => !companies.some(c => c.id === p.company_id));
const visibleProjects = companies.length > 1
? projects.filter(p => p.company_id === activeCompanyId)
: projects;
return (
<>
{grouped.map(({ company, projects: groupProjects }) => (
<div key={company.id}>
{multiCompany && (
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8, marginTop: 16, paddingLeft: 2 }}>
{company.name}
if (visibleProjects.length === 0) return (
<div className="empty-state">
<h3>No projects for this company</h3>
</div>
)}
{groupProjects.map(project => (
<ProjectGroup
key={project.id}
project={project}
tasks={tasks.filter(t => t.project_id === project.id)}
submissions={submissions}
currentUserId={currentUser.id}
filter={filter}
/>
))}
</div>
))}
{ungrouped.map(project => (
<ProjectGroup
key={project.id}
project={project}
tasks={tasks.filter(t => t.project_id === project.id)}
submissions={submissions}
currentUserId={currentUser.id}
filter={filter}
/>
))}
</>
);
return visibleProjects.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>
);