fix: wire New Project button — modal + POST /projects + state refresh
This commit is contained in:
parent
88689a4eb2
commit
506ee2d695
1 changed files with 74 additions and 16 deletions
|
|
@ -1,17 +1,67 @@
|
|||
// screens-projects.jsx
|
||||
|
||||
function NewProjectModal({ onClose, onCreated }) {
|
||||
const [name, setName] = React.useState('');
|
||||
const [saving, setSaving] = React.useState(false);
|
||||
const [err, setErr] = React.useState(null);
|
||||
|
||||
const create = () => {
|
||||
if (!name.trim()) { setErr('Name is required'); return; }
|
||||
setSaving(true); setErr(null);
|
||||
window.ZAMPP_API.fetch('/projects', { method: 'POST', body: JSON.stringify({ name: name.trim() }) })
|
||||
.then(p => { onCreated(p); onClose(); })
|
||||
.catch(e => { setSaving(false); setErr(e.message || 'Failed to create project'); });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal-backdrop" onClick={onClose}>
|
||||
<div className="modal" style={{ width: 420 }} onClick={e => e.stopPropagation()}>
|
||||
<div className="modal-head">
|
||||
<div style={{ fontSize: 15, fontWeight: 600 }}>New project</div>
|
||||
<button className="icon-btn" onClick={onClose}><Icon name="x" /></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="field">
|
||||
<label className="field-label">Project name</label>
|
||||
<input className="field-input" value={name} onChange={e => setName(e.target.value)}
|
||||
placeholder="e.g. Sunday Night Game" autoFocus
|
||||
onKeyDown={e => e.key === 'Enter' && !saving && create()} />
|
||||
</div>
|
||||
{err && <div style={{ fontSize: 12, color: 'var(--danger)', marginTop: 4 }}>{err}</div>}
|
||||
</div>
|
||||
<div className="modal-foot">
|
||||
<button className="btn ghost" onClick={onClose}>Cancel</button>
|
||||
<span style={{ flex: 1 }} />
|
||||
<button className="btn primary" onClick={create} disabled={saving}>
|
||||
{saving ? 'Creating…' : 'Create project'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Projects({ onOpenProject, navigate }) {
|
||||
const { PROJECTS: ALL_PROJECTS, ASSETS } = window.ZAMPP_DATA;
|
||||
const [projects, setProjects] = React.useState(window.ZAMPP_DATA.PROJECTS);
|
||||
const { ASSETS } = window.ZAMPP_DATA;
|
||||
const [search, setSearch] = React.useState('');
|
||||
const [view, setView] = React.useState('grid');
|
||||
let projects = ALL_PROJECTS;
|
||||
if (search) projects = projects.filter(p => p.name.toLowerCase().includes(search.toLowerCase()));
|
||||
const [showNew, setShowNew] = React.useState(false);
|
||||
|
||||
const onCreated = (p) => {
|
||||
const updated = [p, ...window.ZAMPP_DATA.PROJECTS];
|
||||
window.ZAMPP_DATA.PROJECTS = updated;
|
||||
setProjects(updated);
|
||||
};
|
||||
|
||||
let filtered = projects;
|
||||
if (search) filtered = filtered.filter(p => p.name.toLowerCase().includes(search.toLowerCase()));
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-header">
|
||||
<h1>Projects</h1>
|
||||
<span className="subtitle">{projects.length} projects</span>
|
||||
<span className="subtitle">{filtered.length} projects</span>
|
||||
<div className="spacer" />
|
||||
<div className="search" style={{ width: 240 }}>
|
||||
<Icon name="search" className="search-icon" />
|
||||
|
|
@ -21,35 +71,43 @@ function Projects({ onOpenProject, navigate }) {
|
|||
<button className={view === 'grid' ? 'active' : ''} onClick={() => setView('grid')}><Icon name="grid" size={12} /></button>
|
||||
<button className={view === 'list' ? 'active' : ''} onClick={() => setView('list')}><Icon name="list" size={12} /></button>
|
||||
</div>
|
||||
<button className="btn primary"><Icon name="plus" />New project</button>
|
||||
<button className="btn primary" onClick={() => setShowNew(true)}><Icon name="plus" />New project</button>
|
||||
</div>
|
||||
<div className="page-body">
|
||||
{projects.length === 0 ? (
|
||||
<div style={{ padding: 40, textAlign: 'center', color: 'var(--text-3)' }}>No projects yet. Create one to get started.</div>
|
||||
{filtered.length === 0 ? (
|
||||
<div style={{ padding: 40, textAlign: 'center', color: 'var(--text-3)' }}>
|
||||
{search ? 'No matching projects.' : 'No projects yet.'}
|
||||
{!search && (
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<button className="btn primary" onClick={() => setShowNew(true)}><Icon name="plus" />New project</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : view === 'grid' ? (
|
||||
<div className="projects-grid">
|
||||
{projects.map(p => <ProjectCard key={p.id} project={p} assets={ASSETS} onOpen={() => onOpenProject(p)} />)}
|
||||
{filtered.map(p => <ProjectCard key={p.id} project={p} assets={ASSETS} onOpen={() => onOpenProject(p)} />)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="panel">
|
||||
<div className="list-row head" style={{ padding: '12px 16px', gridTemplateColumns: '1fr 100px 120px 120px 80px' }}>
|
||||
<div>Project</div><div>Assets</div><div>Storage</div><div>Updated</div><div></div>
|
||||
</div>
|
||||
{projects.map(p => (
|
||||
{filtered.map(p => (
|
||||
<div key={p.id} className="list-row" style={{ padding: '12px 16px', gridTemplateColumns: '1fr 100px 120px 120px 80px', borderBottom: '1px solid var(--border)', cursor: 'pointer' }} onClick={() => onOpenProject(p)}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{ width: 8, height: 32, borderRadius: 2, background: p.color }} />
|
||||
<div style={{ width: 8, height: 32, borderRadius: 2, background: p.color || 'var(--accent)' }} />
|
||||
<div>{p.name}</div>
|
||||
</div>
|
||||
<div className="col-sub">{p.assets}</div>
|
||||
<div className="col-sub">{p.assets || 0}</div>
|
||||
<div className="col-sub">—</div>
|
||||
<div className="col-sub">{p.updated}</div>
|
||||
<div className="col-sub">{p.updated || '—'}</div>
|
||||
<button className="icon-btn" onClick={e => e.stopPropagation()}><Icon name="more" /></button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showNew && <NewProjectModal onClose={() => setShowNew(false)} onCreated={onCreated} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -69,16 +127,16 @@ function ProjectCard({ project, assets, onOpen }) {
|
|||
</div>
|
||||
<div className="project-card-body">
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span style={{ width: 10, height: 10, borderRadius: 2, background: project.color }} />
|
||||
<span style={{ width: 10, height: 10, borderRadius: 2, background: project.color || 'var(--accent)' }} />
|
||||
<span style={{ fontWeight: 600, fontSize: 14 }}>{project.name}</span>
|
||||
</div>
|
||||
<div className="project-meta">
|
||||
<span>{project.assets} assets</span>
|
||||
<span>{project.assets || 0} assets</span>
|
||||
<span>·</span>
|
||||
<span>updated {project.updated}</span>
|
||||
<span>updated {project.updated || '—'}</span>
|
||||
</div>
|
||||
<div className="project-bar">
|
||||
<div className="project-segment" style={{ width: '70%', background: project.color }} />
|
||||
<div className="project-segment" style={{ width: '70%', background: project.color || 'var(--accent)' }} />
|
||||
<div className="project-segment" style={{ width: '20%', background: 'var(--bg-4)' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue