Add files via upload

This commit is contained in:
jasonMPM
2026-03-06 00:03:06 -06:00
committed by GitHub
parent 118c186881
commit 59619c4ed1
6 changed files with 211 additions and 83 deletions

View File

@@ -5,28 +5,58 @@ import Button from '../UI/Button'
import AgendaPanel from '../Calendar/AgendaPanel'
import useProjectStore from '../../store/useProjectStore'
import useUIStore from '../../store/useUIStore'
import { deleteProject } from '../../api/projects'
import { deleteProject, fetchProjects } from '../../api/projects'
const VIEW_OPTIONS = [
{ key: 'active', label: 'Active' },
{ key: 'archived', label: 'Archived' },
{ key: 'all', label: 'All' },
]
export default function ProjectList({ onRegisterNewProject }) {
const { projects, removeProject } = useProjectStore()
const { projects, removeProject, setProjects } = useProjectStore()
const { sidebarTab, setSidebarTab } = useUIStore()
const [showModal, setShowModal] = useState(false)
const [editing, setEditing] = useState(null)
const [showModal, setShowModal] = useState(false)
const [editing, setEditing] = useState(null)
const [projectView, setProjectView] = useState('active')
const [search, setSearch] = useState('')
useEffect(() => { onRegisterNewProject?.(() => setShowModal(true)) }, [onRegisterNewProject])
useEffect(() => {
onRegisterNewProject?.(() => setShowModal(true))
}, [onRegisterNewProject])
const refreshProjects = async () => {
const data = await fetchProjects()
setProjects(data)
}
const handleEdit = (p) => { setEditing(p); setShowModal(true) }
const handleDelete = async (p) => {
if (window.confirm(`Delete "${p.name}" and all its deliverables?`)) {
await deleteProject(p.id); removeProject(p.id)
await deleteProject(p.id)
removeProject(p.id)
}
}
const handleClose = () => { setShowModal(false); setEditing(null) }
const q = search.trim().toLowerCase()
const visibleProjects = (projects || [])
.filter(p => {
const isArchived = !!p.archived_at
if (projectView === 'active') return !isArchived
if (projectView === 'archived') return isArchived
return true
})
.filter(p => {
if (!q) return true
const hay = `${p.name || ''} ${p.description || ''}`.toLowerCase()
return hay.includes(q)
})
return (
<div className="flex flex-col h-full">
{/* Header — taller to give logo more presence */}
{/* Header */}
<div className="flex items-center gap-3 px-4 py-4 border-b border-surface-border flex-shrink-0 pl-10">
<img
src="/logo.png"
@@ -48,7 +78,11 @@ export default function ProjectList({ onRegisterNewProject }) {
<div className="flex border-b border-surface-border flex-shrink-0">
{[['projects','Projects'],['agenda','Upcoming']].map(([key, label]) => (
<button key={key} onClick={() => setSidebarTab(key)}
className={`flex-1 py-2 text-xs font-semibold transition-colors ${sidebarTab === key ? 'text-gold border-b-2 border-gold' : 'text-text-muted hover:text-text-primary'}`}>
className={`flex-1 py-2 text-xs font-semibold transition-colors ${
sidebarTab === key
? 'text-gold border-b-2 border-gold'
: 'text-text-muted hover:text-text-primary'
}`}>
{label}
</button>
))}
@@ -58,7 +92,41 @@ export default function ProjectList({ onRegisterNewProject }) {
<div className="flex-1 overflow-y-auto">
{sidebarTab === 'projects' ? (
<div className="p-3 space-y-2">
{projects.length === 0 ? (
{/* View toggle + Search + Refresh */}
<div className="flex items-center gap-2 pb-1">
<div className="flex bg-surface-elevated border border-surface-border rounded-lg overflow-hidden flex-shrink-0">
{VIEW_OPTIONS.map(v => (
<button
key={v.key}
onClick={() => setProjectView(v.key)}
className={`px-2.5 py-1.5 text-[10px] font-semibold transition-colors ${
projectView === v.key
? 'bg-gold text-surface'
: 'text-text-muted hover:text-text-primary'
}`}
title={`Show ${v.label.toLowerCase()} projects`}
>
{v.label}
</button>
))}
</div>
<input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search projects…"
className="flex-1 min-w-0 bg-surface-elevated border border-surface-border rounded-lg px-2.5 py-1.5 text-xs text-text-primary placeholder:text-text-muted/50 outline-none focus:border-gold/40 transition-colors"
/>
<button
onClick={refreshProjects}
className="flex-shrink-0 text-[10px] px-2 py-1.5 rounded-lg border border-surface-border bg-surface-elevated text-text-muted hover:text-text-primary hover:border-gold/30 transition-colors"
title="Refresh projects"
>
</button>
</div>
{visibleProjects.length === 0 ? (
<div className="flex flex-col items-center justify-center py-10 px-4 text-center">
<div className="opacity-20 mb-3">
<svg width="56" height="56" viewBox="0 0 56 56" fill="none">
@@ -70,14 +138,16 @@ export default function ProjectList({ onRegisterNewProject }) {
<line x1="17" y1="39" x2="30" y2="39" stroke="#C9A84C" strokeWidth="1.5" strokeDasharray="4 2.5"/>
</svg>
</div>
<p className="text-text-muted text-sm font-medium">No projects yet</p>
<p className="text-text-muted text-sm font-medium">
{q ? 'No matching projects' : projectView === 'archived' ? 'No archived projects' : 'No projects yet'}
</p>
<p className="text-text-muted/50 text-xs mt-1">
Press <kbd className="bg-surface-border px-1.5 py-0.5 rounded text-[10px] font-mono">N</kbd> or click + Project
</p>
</div>
) : (
projects.map(p => (
<ProjectCard key={p.id} project={p} onEdit={handleEdit} onDelete={handleDelete} />
visibleProjects.map(p => (
<ProjectCard key={p.id} project={p} onEdit={handleEdit} onDelete={handleDelete} onArchiveToggle={refreshProjects} />
))
)}
</div>