document types
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
function DocumentTypesPanel({
|
||||
documentTypes = [],
|
||||
onRefresh,
|
||||
onCreate,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
onNotify,
|
||||
}) {
|
||||
const [editingId, setEditingId] = useState(null);
|
||||
const [draftName, setDraftName] = useState('');
|
||||
const [createName, setCreateName] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [deletingId, setDeletingId] = useState(null);
|
||||
|
||||
const startEdit = useCallback((entry) => {
|
||||
setEditingId(entry.id);
|
||||
setDraftName(entry.name || '');
|
||||
}, []);
|
||||
|
||||
const cancelEdit = useCallback(() => {
|
||||
setEditingId(null);
|
||||
setDraftName('');
|
||||
setSaving(false);
|
||||
}, []);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!editingId) return;
|
||||
const trimmed = draftName.trim();
|
||||
if (!trimmed) {
|
||||
onNotify?.('Document type name cannot be empty.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
await onUpdate(editingId, { name: trimmed });
|
||||
cancelEdit();
|
||||
} catch (error) {
|
||||
onNotify?.('Failed to update document type.', 'error');
|
||||
console.error('[document-types] update failed', error);
|
||||
setSaving(false);
|
||||
}
|
||||
}, [editingId, draftName, onUpdate, cancelEdit, onNotify]);
|
||||
|
||||
const handleDelete = useCallback(
|
||||
async (entry) => {
|
||||
if (!entry?.id) return;
|
||||
setDeletingId(entry.id);
|
||||
try {
|
||||
await onDelete(entry.id);
|
||||
if (editingId === entry.id) {
|
||||
cancelEdit();
|
||||
}
|
||||
} catch (error) {
|
||||
onNotify?.('Failed to delete document type.', 'error');
|
||||
console.error('[document-types] delete failed', error);
|
||||
} finally {
|
||||
setDeletingId(null);
|
||||
}
|
||||
},
|
||||
[onDelete, editingId, cancelEdit, onNotify],
|
||||
);
|
||||
|
||||
const handleCreate = useCallback(
|
||||
async (event) => {
|
||||
event.preventDefault();
|
||||
const trimmed = createName.trim();
|
||||
if (!trimmed) {
|
||||
onNotify?.('Document type name cannot be empty.', 'error');
|
||||
return;
|
||||
}
|
||||
setCreating(true);
|
||||
try {
|
||||
await onCreate({ name: trimmed });
|
||||
setCreateName('');
|
||||
} catch (error) {
|
||||
onNotify?.('Failed to create document type.', 'error');
|
||||
console.error('[document-types] create failed', error);
|
||||
} finally {
|
||||
setCreating(false);
|
||||
}
|
||||
},
|
||||
[createName, onCreate, onNotify],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
handleSave();
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
cancelEdit();
|
||||
}
|
||||
},
|
||||
[handleSave, cancelEdit],
|
||||
);
|
||||
|
||||
const renderUsage = useCallback((usage) => {
|
||||
if (!usage) {
|
||||
return '0';
|
||||
}
|
||||
const total = typeof usage.total === 'number' ? usage.total : 0;
|
||||
return total.toString();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="correspondents-panel">
|
||||
<div className="panel-section__header">
|
||||
<div className="panel-section__titles">
|
||||
<h2>Document Types</h2>
|
||||
<div className="panel-section__subtitle">{documentTypes.length} total</div>
|
||||
</div>
|
||||
<div className="header-actions correspondents-actions">
|
||||
<form className="correspondents-actions__form" onSubmit={handleCreate}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="New document type name"
|
||||
value={createName}
|
||||
onChange={(event) => setCreateName(event.target.value)}
|
||||
disabled={creating}
|
||||
/>
|
||||
<button type="submit" disabled={creating || !createName.trim()}>
|
||||
{creating ? 'Creating…' : 'Create'}
|
||||
</button>
|
||||
</form>
|
||||
<button
|
||||
className="secondary"
|
||||
type="button"
|
||||
onClick={onRefresh}
|
||||
disabled={saving || creating || Boolean(deletingId)}
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel-section__body tags-panel__body">
|
||||
{documentTypes.length === 0 ? (
|
||||
<div className="empty-state">No document types created yet.</div>
|
||||
) : (
|
||||
<div className="tags-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col" className="numeric">
|
||||
Usage
|
||||
</th>
|
||||
<th scope="col" className="actions">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{documentTypes.map((entry) => {
|
||||
const isEditing = editingId === entry.id;
|
||||
return (
|
||||
<tr key={entry.id} className={isEditing ? 'editing' : ''}>
|
||||
<td className="tags-table__label">
|
||||
{isEditing ? (
|
||||
<input
|
||||
className="tags-table__label-input"
|
||||
value={draftName}
|
||||
onChange={(event) => setDraftName(event.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={saving}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<span>{entry.name}</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="numeric">{renderUsage(entry.usage)}</td>
|
||||
<td className="actions">
|
||||
{isEditing ? (
|
||||
<div className="tags-table__edit-controls">
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={cancelEdit}
|
||||
disabled={saving}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="tags-table__row-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button ghost"
|
||||
onClick={() => startEdit(entry)}
|
||||
title="Rename"
|
||||
aria-label={`Rename document type ${entry.name}`}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button danger"
|
||||
onClick={() => handleDelete(entry)}
|
||||
disabled={deletingId === entry.id}
|
||||
title="Delete"
|
||||
aria-label={`Delete document type ${entry.name}`}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default DocumentTypesPanel;
|
||||
Reference in New Issue
Block a user