1import { useState, useEffect } from 'react';
2import { useNavigate } from 'react-router-dom';
3import MainLayout from '../components/Layout/MainLayout';
4// import removed: TenantSelector
5import './KnowledgeBase.css';
6import { apiFetch } from '../lib/api';
7import { isAdmin } from '../utils/auth';
9function KnowledgeBase() {
10 const token = localStorage.getItem('token');
14 const payload = token.split('.')[1];
15 const decoded = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
16 tenantId = decoded.tenant_id || decoded.tenantId || '';
19 const navigate = useNavigate();
20 const [documents, setDocuments] = useState([]);
21 const [loading, setLoading] = useState(false);
22 const [error, setError] = useState('');
23 const [page, setPage] = useState(1);
24 const [totalPages, setTotalPages] = useState(1);
25 const [tenantMap, setTenantMap] = useState({});
26 const [selectedDoc, setSelectedDoc] = useState(null);
27 const [editorContent, setEditorContent] = useState('');
28 const [deleteConfirm, setDeleteConfirm] = useState(null);
31 // Optionally fetch tenant map for display, but don't use isRootTenant for column logic
33 const fetchTenantMap = async () => {
35 const token = localStorage.getItem('token');
36 const res = await apiFetch('/tenants', {
37 headers: { Authorization: `Bearer ${token}` }
40 const data = await res.json();
42 data.tenants?.forEach(t => {
43 map[t.tenant_id] = t.name;
48 // Silently fail - 403 is expected for non-MSP users
54 const fetchDocuments = async (fetchTenantId = tenantId) => {
57 const token = localStorage.getItem('token');
58 let url = `/documents?page=${page}&limit=${perPage}`;
59 url += `&tenant_id=${tenantId}`;
60 const res = await apiFetch(url, { headers: { Authorization: `Bearer ${token}` } });
61 if (!res.ok) throw new Error('Failed to load documents');
62 const data = await res.json();
63 setDocuments(data.documents);
64 setTotalPages(Math.ceil(data.total / perPage));
67 setError(err.message);
74 fetchDocuments(tenantId);
77 const handleCreateClick = () => {
78 setSelectedDoc({ title: '', content: '', category: '', doc_id: null });
82 const handleEditClick = (docId) => {
83 const doc = documents.find(d => d.doc_id === docId);
85 setEditorContent(doc?.content || '');
88 const handleSave = async () => {
91 const token = localStorage.getItem('token');
92 const method = selectedDoc.doc_id ? 'PUT' : 'POST';
93 const url = selectedDoc.doc_id ? `/documents/${selectedDoc.doc_id}` : '/documents';
94 const res = await apiFetch(url, {
97 'Content-Type': 'application/json',
98 Authorization: `Bearer ${token}`
100 body: JSON.stringify({ ...selectedDoc, content: editorContent, tenant_id: tenantId })
102 if (!res.ok) throw new Error('Failed to save document');
103 setSelectedDoc(null);
104 fetchDocuments(tenantId);
106 setError(err.message);
112 const handleDelete = async (docId) => {
115 const token = localStorage.getItem('token');
116 const res = await apiFetch(`/documents/${docId}`, {
118 headers: { Authorization: `Bearer ${token}` }
120 if (!res.ok) throw new Error('Failed to delete document');
121 setDeleteConfirm(null);
122 fetchDocuments(tenantId);
124 setError(err.message);
130 const handleDeleteClick = (doc) => {
131 setDeleteConfirm(doc);
134 // Show tenant column if any document has tenant_id
135 const showTenantColumn = documents.some(doc => doc.tenant_id !== undefined && doc.tenant_id !== null);
139 <div className="page-content">
140 <div className="page-header">
141 <h2>Documentation</h2>
142 <button className="btn" onClick={handleCreateClick}>
143 <span className="material-symbols-outlined">add</span>
147 {error && <div className="error-message">{error}</div>}
149 <div className="loading">Loading documents...</div>
151 <div className="editor-container">
154 value={selectedDoc.title}
155 onChange={e => setSelectedDoc({ ...selectedDoc, title: e.target.value })}
157 style={{ width: '100%', marginBottom: 8, padding: 8 }}
161 value={selectedDoc.category}
162 onChange={e => setSelectedDoc({ ...selectedDoc, category: e.target.value })}
163 placeholder="Category"
164 style={{ width: '100%', marginBottom: 8, padding: 8 }}
167 value={editorContent}
168 onChange={e => setEditorContent(e.target.value)}
169 placeholder="Document content..."
170 style={{ width: '100%', minHeight: 300, marginBottom: 16, padding: 8, borderRadius: 4, background: '#fff', color: '#222' }}
173 <button className="btn" onClick={handleSave} style={{ marginRight: 8 }}>Save</button>
174 <button className="btn" onClick={() => setSelectedDoc(null)}>Cancel</button>
179 <div className="table-container">
180 <table className="data-table">
185 {showTenantColumn && <th>Tenant</th>}
187 <th>Last Updated</th>
193 {documents.map(doc => (
194 <tr key={doc.doc_id}>
195 <td>#{doc.doc_id}</td>
197 {showTenantColumn && (
198 <td>{doc.tenant_id === 1
200 : (doc.tenant_name || tenantMap[doc.tenant_id] || '-')}</td>
202 <td>{doc.category}</td>
203 <td>{new Date(doc.updated_at).toLocaleDateString()}</td>
204 <td>{doc.author_name}</td>
208 onClick={() => handleEditClick(doc.doc_id)}
209 style={{ marginRight: '8px' }}
211 <span className="material-symbols-outlined">edit</span>
215 className="btn danger"
216 onClick={() => handleDeleteClick(doc)}
218 <span className="material-symbols-outlined">delete</span>
227 <div className="pagination">
228 <button className="btn" disabled={page === 1} onClick={() => setPage(p => p - 1)}>
229 <span className="material-symbols-outlined">navigate_before</span>
232 Page {page} of {totalPages}
234 <button className="btn" disabled={page === totalPages} onClick={() => setPage(p => p + 1)}>
235 <span className="material-symbols-outlined">navigate_next</span>
242 <div className="modal-overlay" onClick={() => setDeleteConfirm(null)}>
243 <div className="modal-content" onClick={e => e.stopPropagation()}>
244 <div className="modal-header">
245 <h3>Confirm Delete</h3>
248 onClick={() => setDeleteConfirm(null)}
249 style={{ padding: '4px', minWidth: 'auto' }}
251 <span className="material-symbols-outlined">close</span>
254 <div className="modal-body">
255 <p>Are you sure you want to delete the document:</p>
256 <p><strong>"{deleteConfirm.title}"</strong></p>
257 <p style={{ color: 'var(--error)', fontSize: '0.9em' }}>
258 This action cannot be undone.
261 <div className="modal-footer">
264 onClick={() => setDeleteConfirm(null)}
265 style={{ marginRight: '8px' }}
270 className="btn danger"
271 onClick={() => handleDelete(deleteConfirm.doc_id)}
284export default KnowledgeBase;