Phase 1 — Critical Fixes: - Fix SOAP instructions not clearing on Clear button - Show transcription provider (AWS/OpenAI) in UI toast - Fix silent transcription failures in dictation and SOAP modules - Add IndexedDB audio backup system (24hr retention, retry from Settings) - Prevent duplicate encounter saves with idempotency keys - Add Save/Load/New bar to SOAP note generator Phase 2 — Features: - Dragon-like AI memory: auto-track user corrections, inject into prompts - Per-section template categories (SOAP, HPI, well visit, sick visit) - Bigger textarea for SOAP instructions - S3 document upload/management (AWS S3, Backblaze B2, MinIO compatible) - Faster transcription via lower bitrate recording (16kbps opus) Phase 3 — APK & CI/CD: - GitHub Actions: Docker build+push on version tags - GitHub Actions: TWA APK build for Obtainium auto-updates - Android TWA project with foreground service for background recording - Enhanced PWA manifest with shortcuts and maskable icons
123 lines
5.2 KiB
JavaScript
123 lines
5.2 KiB
JavaScript
// ============================================================
|
|
// DOCUMENTS.JS — S3 document upload & management UI
|
|
// ============================================================
|
|
|
|
(function() {
|
|
|
|
function loadDocuments() {
|
|
var container = document.getElementById('documents-list');
|
|
if (!container) return;
|
|
fetch('/api/documents', { headers: getAuthHeaders() })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (!data.s3_configured) {
|
|
container.innerHTML = '<p style="color:var(--g400);font-size:13px;">S3 storage not configured. Set S3_BUCKET in server environment.</p>';
|
|
var uploadArea = document.getElementById('doc-upload-area');
|
|
if (uploadArea) uploadArea.style.display = 'none';
|
|
return;
|
|
}
|
|
var docs = data.documents || [];
|
|
if (docs.length === 0) {
|
|
container.innerHTML = '<p style="color:var(--g400);font-size:13px;">No documents uploaded yet.</p>';
|
|
return;
|
|
}
|
|
container.innerHTML = docs.map(function(doc) {
|
|
var sizeStr = doc.size_bytes < 1024 ? doc.size_bytes + ' B' :
|
|
doc.size_bytes < 1048576 ? Math.round(doc.size_bytes / 1024) + ' KB' :
|
|
(doc.size_bytes / 1048576).toFixed(1) + ' MB';
|
|
var date = doc.created_at ? new Date(doc.created_at).toLocaleDateString() : '';
|
|
return '<div class="saved-enc-item" style="padding:8px 12px;">' +
|
|
'<div style="flex:1;min-width:0;">' +
|
|
'<div style="font-weight:600;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' +
|
|
'<i class="fas fa-file" style="margin-right:4px;color:var(--g400);"></i>' + esc(doc.filename) +
|
|
'</div>' +
|
|
'<div style="font-size:11px;color:var(--g500);">' + sizeStr + ' · ' + date +
|
|
(doc.description ? ' · ' + esc(doc.description) : '') +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<button class="btn-sm btn-primary doc-download-btn" data-id="' + doc.id + '"><i class="fas fa-download"></i></button>' +
|
|
'<button class="btn-sm btn-ghost doc-delete-btn" data-id="' + doc.id + '" style="color:var(--red);"><i class="fas fa-trash"></i></button>' +
|
|
'</div>';
|
|
}).join('');
|
|
|
|
container.querySelectorAll('.doc-download-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function() { downloadDocument(btn.dataset.id); });
|
|
});
|
|
container.querySelectorAll('.doc-delete-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function() { deleteDocument(btn.dataset.id); });
|
|
});
|
|
})
|
|
.catch(function() {
|
|
container.innerHTML = '<p style="color:var(--g400);font-size:13px;">Failed to load documents.</p>';
|
|
});
|
|
}
|
|
|
|
function uploadDocument() {
|
|
var fileInput = document.getElementById('doc-file-input');
|
|
var descInput = document.getElementById('doc-description');
|
|
if (!fileInput || !fileInput.files[0]) { showToast('Select a file first', 'error'); return; }
|
|
|
|
var formData = new FormData();
|
|
formData.append('file', fileInput.files[0]);
|
|
formData.append('description', descInput ? descInput.value : '');
|
|
|
|
showLoading('Uploading document...');
|
|
fetch('/api/documents/upload', {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Bearer ' + (window.AUTH_TOKEN || localStorage.getItem('ped_scribe_token') || '') },
|
|
body: formData
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
hideLoading();
|
|
if (data.success) {
|
|
showToast('Document uploaded: ' + data.filename, 'success');
|
|
fileInput.value = '';
|
|
if (descInput) descInput.value = '';
|
|
loadDocuments();
|
|
} else {
|
|
showToast(data.error || 'Upload failed', 'error');
|
|
}
|
|
})
|
|
.catch(function(err) { hideLoading(); showToast('Upload failed: ' + err.message, 'error'); });
|
|
}
|
|
|
|
function downloadDocument(id) {
|
|
fetch('/api/documents/' + id + '/download', { headers: getAuthHeaders() })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success && data.url) {
|
|
window.open(data.url, '_blank');
|
|
} else {
|
|
showToast(data.error || 'Download failed', 'error');
|
|
}
|
|
})
|
|
.catch(function() { showToast('Download failed', 'error'); });
|
|
}
|
|
|
|
function deleteDocument(id) {
|
|
if (!confirm('Delete this document permanently?')) return;
|
|
fetch('/api/documents/' + id, { method: 'DELETE', headers: getAuthHeaders() })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success) { showToast('Document deleted', 'info'); loadDocuments(); }
|
|
else showToast(data.error || 'Delete failed', 'error');
|
|
})
|
|
.catch(function() { showToast('Delete failed', 'error'); });
|
|
}
|
|
|
|
function esc(str) {
|
|
if (!str) return '';
|
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
}
|
|
|
|
// Expose for settings tab load
|
|
window.loadDocuments = loadDocuments;
|
|
|
|
// Wire upload button
|
|
document.addEventListener('click', function(e) {
|
|
if (e.target.closest('#btn-doc-upload')) uploadDocument();
|
|
});
|
|
|
|
console.log('Documents module loaded');
|
|
})();
|