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:
@@ -9,7 +9,7 @@ import { withTimeout } from '../../lib/withTimeout';
|
|||||||
const rLabel = (v) => 'R' + String(v || 0).padStart(2, '0');
|
const rLabel = (v) => 'R' + String(v || 0).padStart(2, '0');
|
||||||
|
|
||||||
function ProjectGroup({ project, tasks, submissions, currentUserId, filter }) {
|
function ProjectGroup({ project, tasks, submissions, currentUserId, filter }) {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const filteredTasks = filter === 'mine'
|
const filteredTasks = filter === 'mine'
|
||||||
? tasks.filter(task => {
|
? tasks.filter(task => {
|
||||||
@@ -115,6 +115,8 @@ export default function MyProjects() {
|
|||||||
const [submissions, setSubmissions] = useState([]);
|
const [submissions, setSubmissions] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [filter, setFilter] = useState('all'); // 'all' | 'mine'
|
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(() => {
|
useEffect(() => {
|
||||||
async function load() {
|
async function load() {
|
||||||
@@ -187,6 +189,27 @@ export default function MyProjects() {
|
|||||||
</div>
|
</div>
|
||||||
</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 ? (
|
{projects.length === 0 ? (
|
||||||
<div className="empty-state">
|
<div className="empty-state">
|
||||||
<h3>No projects yet</h3>
|
<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>
|
<Link to="/new-project" className="btn btn-primary" style={{ marginTop: 16 }}>+ New Project</Link>
|
||||||
</div>
|
</div>
|
||||||
) : (() => {
|
) : (() => {
|
||||||
const companies = currentUser.companies || (currentUser.company ? [currentUser.company] : []);
|
const visibleProjects = companies.length > 1
|
||||||
const multiCompany = companies.length > 1;
|
? projects.filter(p => p.company_id === activeCompanyId)
|
||||||
const companyMap = Object.fromEntries(companies.map(c => [c.id, c.name]));
|
: projects;
|
||||||
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));
|
|
||||||
|
|
||||||
return (
|
if (visibleProjects.length === 0) return (
|
||||||
<>
|
<div className="empty-state">
|
||||||
{grouped.map(({ company, projects: groupProjects }) => (
|
<h3>No projects for this company</h3>
|
||||||
<div key={company.id}>
|
</div>
|
||||||
{multiCompany && (
|
|
||||||
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8, marginTop: 16, paddingLeft: 2 }}>
|
|
||||||
{company.name}
|
|
||||||
</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>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user