Files
HarborForge.Frontend/src/components/KnowledgeBaseFormModal.tsx
hzhang 14ac03b551 feat(knowledge-base): Knowledge Base UI — browse/edit, modal, project links
- Knowledge Bases list page + sidebar entry + "+ New" create modal
- Detail page with a recursive structure tree: add/edit/delete topics,
  categories and facts inline, including name + description editing
- Create/metadata-edit modal (title, description)
- Project edit modal gains a link/remove knowledge base section
- Types and routes for /knowledge-bases and /knowledge-bases/:id
- Scoped .kb-* styles (contained panel, topic cards, hierarchy guides)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 15:03:50 +01:00

105 lines
3.2 KiB
TypeScript

import { useEffect, useState } from 'react'
import api from '@/services/api'
import type { KnowledgeBase } from '@/types'
type Props = {
isOpen: boolean
onClose: () => void
onSaved?: (kb: KnowledgeBase) => void | Promise<void>
knowledgeBase?: KnowledgeBase | null
}
type FormState = {
title: string
description: string
}
const emptyForm: FormState = { title: '', description: '' }
export default function KnowledgeBaseFormModal({ isOpen, onClose, onSaved, knowledgeBase }: Props) {
const [saving, setSaving] = useState(false)
const [error, setError] = useState('')
const [form, setForm] = useState<FormState>(emptyForm)
useEffect(() => {
if (!isOpen) return
setError('')
if (knowledgeBase) {
setForm({ title: knowledgeBase.title, description: knowledgeBase.description || '' })
} else {
setForm(emptyForm)
}
}, [isOpen, knowledgeBase])
const submit = async (e: React.FormEvent) => {
e.preventDefault()
setSaving(true)
setError('')
try {
if (knowledgeBase) {
const { data } = await api.patch<KnowledgeBase>(`/knowledge-bases/${knowledgeBase.id}`, {
title: form.title,
description: form.description || null,
})
await onSaved?.(data)
} else {
const { data } = await api.post<KnowledgeBase>('/knowledge-bases', {
title: form.title,
description: form.description || null,
})
await onSaved?.(data)
}
onClose()
} catch (err: any) {
setError(err?.response?.data?.detail || 'Failed to save knowledge base')
} finally {
setSaving(false)
}
}
if (!isOpen) return null
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<h3>{knowledgeBase ? 'Edit Knowledge Base' : 'Create Knowledge Base'}</h3>
<button type="button" className="btn-secondary" onClick={onClose}></button>
</div>
<form className="task-create-form" onSubmit={submit}>
<label>
Title
<input
data-testid="kb-title-input"
required
placeholder="Knowledge base title"
value={form.title}
onChange={(e) => setForm((f) => ({ ...f, title: e.target.value }))}
/>
</label>
<label>
Description
<textarea
data-testid="kb-description-input"
placeholder="Description (optional)"
value={form.description}
onChange={(e) => setForm((f) => ({ ...f, description: e.target.value }))}
/>
</label>
{error && <p className="error-text" style={{ color: 'var(--danger, #e5534b)' }}>{error}</p>}
<div className="modal-actions">
<button type="submit" className="btn-primary" disabled={saving}>
{saving ? 'Saving...' : (knowledgeBase ? 'Save' : 'Create')}
</button>
<button type="button" className="btn-secondary" onClick={onClose} disabled={saving}>Cancel</button>
</div>
</form>
</div>
</div>
)
}