@imola-solutions/documents
v0.4.0
Published
Document management module: hub, folder tree, upload/detail dialogs, and file-storage gallery. Consumers inject apiClient (document + folder services).
Downloads
307
Readme
@imola-solutions/documents
Reusable document-management module for React apps. Extracted from BOPC-ITMP. Provides:
- A document hub UI:
FolderTree,DocumentDetailDrawer,DocumentUploadDialog,RelateDocumentDialog, and folder dialogs. - A generic file-storage gallery:
StorageProvider+StorageView(grid/list) withItemCard,ItemRow,ItemModal, filters, pagination, stats, and an uploader. - Injectable service factories (
createDocumentService,createFolderService) withonAuditandaiExtensionshooks.
Install
npm install @imola-solutions/documents @imola-solutions/uiPeer dependencies (must be installed in the host app):
react/react-dom>= 18@mui/material>= 5 and@emotion/react/@emotion/styled>= 11@phosphor-icons/react>= 2dayjs>= 1
Optional:
sonner(used byDocumentDetailDrawerandRelateDocumentDialogfor toasts; if missing, toasts are silently skipped).recharts(used byStatsfor the radial-bar chart; only importStatsif the host app has recharts installed).
Setup
1. Build a document service
createDocumentService binds the module to the host app's Supabase-compatible API client and current user.
import { createDocumentService } from "@imola-solutions/documents";
import { createClient } from "@/lib/services/client";
const documentService = createDocumentService({
apiClient: createClient(),
currentUser: {
id: "USR-000",
name: "Sofia Rivers",
email: "[email protected]",
},
onAudit: (event) => auditLog.record(event),
aiExtensions: {
summarizeDocument: async (doc) => aiClient.summarize(doc),
extractMetadata: async (doc) => aiClient.extract(doc),
suggestFolder: async (doc, folders) => aiClient.classify(doc, folders),
classifyDocument: async (doc) => aiClient.classify(doc),
},
});The service returns:
uploadDocument({ file, recordType, recordId, metadata })listDocuments(recordType, recordId)deleteDocument(documentId, filePath)getDocumentUrl(filePath, expiresIn?)summarizeDocument(docOrId)/extractMetadata(docOrId)/suggestFolder(doc, folders)/classifyDocument(doc)— throw when the matchingaiExtensionsslot isn't configuredhasAi—{ summarize, extractMetadata, suggestFolder, classify }capability flagsbytesToSize(bytes)— helper
Audit events emitted:
document.upload—{ targetType: "document", targetId, timestamp, success, metadata: { size, type } }document.download—{ targetType: "document", targetId, metadata: { filePath } }document.delete—{ targetType: "document", targetId }document.list—{ metadata: { recordType, recordId, count } }ai.summarize/ai.extract-metadata/ai.suggest-folder/ai.classify
2. Build a folder service (optional, only for the document hub UI)
import { createFolderService } from "@imola-solutions/documents";
const folderService = createFolderService({
apiFetch: window.fetch.bind(window),
apiBase: import.meta.env.VITE_API_URL || "",
onAudit: (event) => auditLog.record(event),
});Returns { listFolders, createFolder, updateFolder, deleteFolder, shareFolder, moveDocument, folderHistory, copyDocument }. Each returns { data, error }.
Audit events emitted: folder.create, folder.update, folder.delete, folder.share, folder.move-document, folder.copy-document.
3. Wire the file-storage gallery
import { StorageProvider, StorageView, ItemsFilters, ItemsPagination, Stats, UploadButton } from "@imola-solutions/documents";
<StorageProvider items={items} onAudit={auditLog.record}>
<ItemsFilters
filters={{ query: "" }}
view="grid"
sortDir="desc"
onNavigate={({ view, sortDir, query }) => {
// update url / state as needed
}}
/>
<StorageView view="grid" />
<ItemsPagination count={items.length} page={0} />
<Stats />
<UploadButton />
</StorageProvider>Storage onAudit events: storage.item.delete, storage.item.favorite.
4. Wire the document hub
import {
FolderTree,
DocumentDetailDrawer,
DocumentUploadDialog,
RelateDocumentDialog,
FolderFormDialog,
} from "@imola-solutions/documents";
<FolderTree
folders={folders}
selectedFolderId={selectedFolderId}
onSelectFolder={setSelectedFolderId}
onDropDocument={(docUid, folderId) => folderService.moveDocument(docUid, folderId)}
onCreate={(parentId) => openCreateDialog(parentId)}
onRename={openRenameDialog}
onShare={openShareDialog}
onArchive={onArchive}
onDelete={onDelete}
onChangeVisibility={openChangeVisibilityDialog}
onHistory={openHistoryDialog}
canCreate
/>
<DocumentDetailDrawer
open={Boolean(currentDoc)}
doc={currentDoc}
userId={currentUser.id}
userName={currentUser.name}
documentService={documentService}
onClose={() => setCurrentDoc(null)}
onRelate={openRelate}
onOpenRelated={setCurrentDoc}
onNavigateToRecord={(recordType, recordId) => navigate(`/dashboard/${recordType}/${recordId}`)}
/>
<DocumentUploadDialog
open={uploadOpen}
documentService={documentService}
folderService={folderService}
onClose={() => setUploadOpen(false)}
onUploaded={(doc) => refreshList()}
defaultRecordType="document"
/>Behavioural changes vs. BOPC-ITMP
- React-router-dom coupling removed — navigation is a callback prop.
@/pathscoupling removed.- Governance / workflow / personal-tag panels in
DocumentDetailDrawerare opt-in: pass agovernanceService,versionService,personalTagsService,accessLogServiceprop (eachnulldisables the corresponding section). - Stable default references at module level in
StorageProviderto prevent an effect from re-running on every render. sonnerandrechartsare treated as optional at import time.
Data model
The document service expects, in the host app's database:
- A
documentsbucket in Supabase storage. - A
document_attachmentstable with columns:id, record_type, record_id, file_name, file_path, file_size, file_type, uploaded_by, uploaded_at, metadata.
