147 lines
5.8 KiB
TypeScript
147 lines
5.8 KiB
TypeScript
import type { FormEvent } from 'react'
|
|
import type { ProjectCreate, ProjectRead } from '../../types'
|
|
|
|
const TECHNICAL_PROJECT_PATTERN = /^(GeoIntel Detection Quality Matrix|GeoIntel hard-negative|GeoIntel training|Mol Building QA)/i
|
|
|
|
function isTechnicalProject(project: ProjectRead): boolean {
|
|
return TECHNICAL_PROJECT_PATTERN.test(project.name)
|
|
}
|
|
|
|
interface ProjectPanelProps {
|
|
projects: ProjectRead[]
|
|
selectedProjectId: string | null
|
|
loadingProjects: boolean
|
|
projectForm: ProjectCreate
|
|
loadingDemoWorkflow: boolean
|
|
demoWorkflowMessage: string | null
|
|
onCreateProject: (event: FormEvent<HTMLFormElement>) => void
|
|
onUpdateProjectForm: (projectForm: ProjectCreate) => void
|
|
onSelectProject: (projectId: string) => void
|
|
onLoadDemoWorkflow: () => void
|
|
}
|
|
|
|
export function ProjectPanel({
|
|
projects,
|
|
selectedProjectId,
|
|
loadingProjects,
|
|
projectForm,
|
|
loadingDemoWorkflow,
|
|
demoWorkflowMessage,
|
|
onCreateProject,
|
|
onUpdateProjectForm,
|
|
onSelectProject,
|
|
onLoadDemoWorkflow,
|
|
}: ProjectPanelProps): JSX.Element {
|
|
const selectedProject = projects.find((project) => project.id === selectedProjectId)
|
|
const regularProjects = projects.filter((project) => !isTechnicalProject(project))
|
|
const technicalProjects = projects.filter(isTechnicalProject)
|
|
const visibleProjects = selectedProject && isTechnicalProject(selectedProject)
|
|
? [selectedProject, ...regularProjects]
|
|
: regularProjects
|
|
|
|
return (
|
|
<section className="workspace-panel" data-testid="project-panel">
|
|
<div className="panel-title-row">
|
|
<div>
|
|
<p className="eyebrow">Gegevenscontext</p>
|
|
<h2>Werkruimtes</h2>
|
|
</div>
|
|
<span className="count-pill">{regularProjects.length} beschikbaar</span>
|
|
</div>
|
|
{loadingProjects ? <p>Werkruimtes laden...</p> : null}
|
|
|
|
<div className="data-selection-summary data-selection-summary-project">
|
|
<span>Actieve werkruimte</span>
|
|
<strong>{selectedProject?.name ?? 'Geen werkruimte actief'}</strong>
|
|
<small>{selectedProject?.region ?? 'De regionale werkruimte wordt automatisch gekozen.'}</small>
|
|
</div>
|
|
|
|
<div className="data-panel-list-block">
|
|
<p className="data-section-label">Beschikbare werkruimtes</p>
|
|
<ul className="entity-list">
|
|
{visibleProjects.map((project) => (
|
|
<li key={project.id}>
|
|
<button
|
|
className={project.id === selectedProjectId ? 'entity-card entity-card-active' : 'entity-card'}
|
|
type="button"
|
|
onClick={() => onSelectProject(project.id)}
|
|
aria-pressed={project.id === selectedProjectId}
|
|
data-testid={`project-select-${project.id}`}
|
|
>
|
|
<strong>{project.name}</strong>
|
|
<span>{project.region || 'Regio niet opgegeven'}</span>
|
|
</button>
|
|
</li>
|
|
))}
|
|
{regularProjects.length === 0 ? (
|
|
<li className="empty-state">
|
|
<strong>Nog geen werkruimte beschikbaar</strong>
|
|
<p>Maak een werkruimte aan of laad de demonstratiegegevens.</p>
|
|
</li>
|
|
) : null}
|
|
</ul>
|
|
</div>
|
|
|
|
<details className="data-panel-form-block technical-management-block">
|
|
<summary>Geavanceerd projectbeheer</summary>
|
|
<div className="technical-management-body">
|
|
<form className="compact-form" onSubmit={onCreateProject}>
|
|
<label>
|
|
Naam
|
|
<input
|
|
value={projectForm.name}
|
|
onChange={(event) => onUpdateProjectForm({ ...projectForm, name: event.target.value })}
|
|
placeholder="Naam van de werkruimte"
|
|
/>
|
|
</label>
|
|
<label>
|
|
Beschrijving
|
|
<input
|
|
value={projectForm.description ?? ''}
|
|
onChange={(event) => onUpdateProjectForm({ ...projectForm, description: event.target.value })}
|
|
placeholder="Korte beschrijving"
|
|
/>
|
|
</label>
|
|
<label>
|
|
Regio
|
|
<input
|
|
value={projectForm.region ?? 'Kempen'}
|
|
onChange={(event) => onUpdateProjectForm({ ...projectForm, region: event.target.value })}
|
|
placeholder="Regio"
|
|
/>
|
|
</label>
|
|
<button className="primary-action" type="submit">Nieuwe werkruimte</button>
|
|
</form>
|
|
<div className="demo-actions">
|
|
<button className="secondary-action" type="button" onClick={onLoadDemoWorkflow} disabled={loadingDemoWorkflow} data-testid="load-demo-workflow">
|
|
{loadingDemoWorkflow ? 'Demo laden...' : 'Demonstratiegegevens laden'}
|
|
</button>
|
|
{demoWorkflowMessage ? <p>{demoWorkflowMessage}</p> : null}
|
|
</div>
|
|
{technicalProjects.length > 0 ? (
|
|
<details className="technical-run-list">
|
|
<summary>{technicalProjects.length} technische test- en benchmarkruns</summary>
|
|
<ul className="entity-list">
|
|
{technicalProjects.map((project) => (
|
|
<li key={project.id}>
|
|
<button
|
|
className={project.id === selectedProjectId ? 'entity-card entity-card-active' : 'entity-card'}
|
|
type="button"
|
|
onClick={() => onSelectProject(project.id)}
|
|
aria-pressed={project.id === selectedProjectId}
|
|
data-testid={`project-select-${project.id}`}
|
|
>
|
|
<strong>{project.name}</strong>
|
|
<span>{project.region || 'Technische run'}</span>
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</details>
|
|
) : null}
|
|
</div>
|
|
</details>
|
|
</section>
|
|
)
|
|
}
|