Rename client project buttons to '+ New Project'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,8 +4,9 @@ import Layout from '../../components/Layout';
|
|||||||
import StatusBadge from '../../components/StatusBadge';
|
import StatusBadge from '../../components/StatusBadge';
|
||||||
import { supabase } from '../../lib/supabase';
|
import { supabase } from '../../lib/supabase';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
|
import { withTimeout } from '../../lib/withTimeout';
|
||||||
|
|
||||||
const vLabel = (v) => 'v' + 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(true);
|
||||||
@@ -20,9 +21,10 @@ function ProjectGroup({ project, tasks, submissions, currentUserId, filter }) {
|
|||||||
if (filter === 'mine' && filteredTasks.length === 0) return null;
|
if (filter === 'mine' && filteredTasks.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden', marginBottom: 8 }}>
|
<div className="interactive-surface" style={{ border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden', marginBottom: 8 }}>
|
||||||
{/* Project header — clickable to collapse */}
|
{/* Project header — clickable to collapse */}
|
||||||
<button
|
<button
|
||||||
|
className="interactive-panel-toggle"
|
||||||
onClick={() => setOpen(o => !o)}
|
onClick={() => setOpen(o => !o)}
|
||||||
style={{
|
style={{
|
||||||
width: '100%', display: 'flex', alignItems: 'center',
|
width: '100%', display: 'flex', alignItems: 'center',
|
||||||
@@ -68,6 +70,7 @@ function ProjectGroup({ project, tasks, submissions, currentUserId, filter }) {
|
|||||||
<Link
|
<Link
|
||||||
key={task.id}
|
key={task.id}
|
||||||
to={`/my-requests/${task.id}`}
|
to={`/my-requests/${task.id}`}
|
||||||
|
className="interactive-row"
|
||||||
style={{
|
style={{
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||||
padding: '12px 16px',
|
padding: '12px 16px',
|
||||||
@@ -81,7 +84,7 @@ function ProjectGroup({ project, tasks, submissions, currentUserId, filter }) {
|
|||||||
{task.title}
|
{task.title}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>
|
<span style={{ fontSize: 12, color: 'var(--text-muted)' }}>
|
||||||
{vLabel(task.current_version)}
|
{rLabel(task.current_version)}
|
||||||
</span>
|
</span>
|
||||||
{isMine && (
|
{isMine && (
|
||||||
<span style={{ fontSize: 11, background: 'rgba(245,165,35,0.15)', color: 'var(--accent)', padding: '1px 7px', borderRadius: 4, fontWeight: 600 }}>
|
<span style={{ fontSize: 11, background: 'rgba(245,165,35,0.15)', color: 'var(--accent)', padding: '1px 7px', borderRadius: 4, fontWeight: 600 }}>
|
||||||
@@ -123,23 +126,36 @@ export default function MyProjects() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function load() {
|
async function load() {
|
||||||
const [{ data: p }, { data: t }] = await Promise.all([
|
try {
|
||||||
supabase.from('projects').select('*').order('created_at', { ascending: false }),
|
const [{ data: p }, { data: t }] = await withTimeout(Promise.all([
|
||||||
supabase.from('tasks').select('*').order('submitted_at', { ascending: false }),
|
supabase.from('projects').select('*').order('created_at', { ascending: false }),
|
||||||
]);
|
supabase.from('tasks').select('*').order('submitted_at', { ascending: false }),
|
||||||
setProjects(p || []);
|
]), 12000, 'Projects load');
|
||||||
setTasks(t || []);
|
setProjects(p || []);
|
||||||
|
setTasks(t || []);
|
||||||
|
|
||||||
if (t && t.length > 0) {
|
if (t && t.length > 0) {
|
||||||
const { data: subs } = await supabase
|
const { data: subs } = await withTimeout(
|
||||||
.from('submissions')
|
supabase
|
||||||
.select('id, task_id, submitted_by, submitted_by_name, version_number, type')
|
.from('submissions')
|
||||||
.in('task_id', t.map(task => task.id))
|
.select('id, task_id, submitted_by, submitted_by_name, version_number, type')
|
||||||
.order('version_number');
|
.in('task_id', t.map(task => task.id))
|
||||||
setSubmissions(subs || []);
|
.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
load();
|
load();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -153,30 +169,37 @@ export default function MyProjects() {
|
|||||||
<div className="page-title">Projects</div>
|
<div className="page-title">Projects</div>
|
||||||
<div className="page-subtitle">All work for your company.</div>
|
<div className="page-subtitle">All work for your company.</div>
|
||||||
</div>
|
</div>
|
||||||
<Link to="/new-request" className="btn btn-primary">+ New Request</Link>
|
<Link to="/new-request" className="btn btn-primary">+ New Project</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filter toggle */}
|
<div className="card page-toolbar">
|
||||||
<div style={{ display: 'flex', gap: 8, marginBottom: 20 }}>
|
<div className="page-toolbar-grid">
|
||||||
<button
|
<div className="page-toolbar-section">
|
||||||
className={`btn btn-sm ${filter === 'all' ? 'btn-primary' : 'btn-outline'}`}
|
<div className="card-title" style={{ marginBottom: 10 }}>Filter</div>
|
||||||
onClick={() => setFilter('all')}
|
<div className="page-toolbar-filters">
|
||||||
>
|
<button
|
||||||
All Requests
|
className={`btn btn-sm ${filter === 'all' ? 'btn-primary' : 'btn-outline'}`}
|
||||||
</button>
|
onClick={() => setFilter('all')}
|
||||||
<button
|
>
|
||||||
className={`btn btn-sm ${filter === 'mine' ? 'btn-primary' : 'btn-outline'}`}
|
All Requests
|
||||||
onClick={() => setFilter('mine')}
|
</button>
|
||||||
>
|
<button
|
||||||
Mine Only
|
className={`btn btn-sm ${filter === 'mine' ? 'btn-primary' : 'btn-outline'}`}
|
||||||
</button>
|
onClick={() => setFilter('mine')}
|
||||||
|
>
|
||||||
|
Mine Only
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</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>
|
||||||
<p>Submit a request and a project will be created automatically.</p>
|
<p>Submit a request and a project will be created automatically.</p>
|
||||||
<Link to="/new-request" className="btn btn-primary" style={{ marginTop: 16 }}>Submit Request</Link>
|
<Link to="/new-request" className="btn btn-primary" style={{ marginTop: 16 }}>+ New Project</Link>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
projects.map(project => (
|
projects.map(project => (
|
||||||
|
|||||||
Reference in New Issue
Block a user