668 lines
26 KiB
HTML
668 lines
26 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Data Synchronization - {{ block.super }}{% endblock %}
|
|
|
|
{% block css %}
|
|
<style>
|
|
.sync-dashboard {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border-radius: 10px;
|
|
padding: 30px;
|
|
margin-bottom: 30px;
|
|
}
|
|
.sync-card {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin: 15px 0;
|
|
border-left: 4px solid #007bff;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.sync-active { border-left-color: #28a745; }
|
|
.sync-error { border-left-color: #dc3545; }
|
|
.sync-warning { border-left-color: #ffc107; }
|
|
.sync-pending { border-left-color: #6c757d; }
|
|
.progress-container {
|
|
background: #f8f9fa;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin: 15px 0;
|
|
}
|
|
.sync-progress {
|
|
height: 25px;
|
|
background: #e9ecef;
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
margin: 10px 0;
|
|
}
|
|
.progress-bar-animated {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #007bff, #0056b3);
|
|
transition: width 0.3s ease;
|
|
position: relative;
|
|
}
|
|
.progress-bar-animated::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
bottom: 0;
|
|
right: 0;
|
|
background: linear-gradient(
|
|
45deg,
|
|
rgba(255,255,255,.2) 25%,
|
|
transparent 25%,
|
|
transparent 50%,
|
|
rgba(255,255,255,.2) 50%,
|
|
rgba(255,255,255,.2) 75%,
|
|
transparent 75%,
|
|
transparent
|
|
);
|
|
background-size: 30px 30px;
|
|
animation: move 1s linear infinite;
|
|
}
|
|
@keyframes move {
|
|
0% { background-position: 0 0; }
|
|
100% { background-position: 30px 30px; }
|
|
}
|
|
.progress-text {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
font-weight: bold;
|
|
color: #333;
|
|
font-size: 0.9rem;
|
|
}
|
|
.data-source {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin: 10px 0;
|
|
border: 1px solid #dee2e6;
|
|
transition: all 0.3s ease;
|
|
}
|
|
.data-source:hover {
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
transform: translateY(-2px);
|
|
}
|
|
.source-status {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
margin-right: 8px;
|
|
}
|
|
.status-syncing { background: #007bff; animation: pulse 1.5s infinite; }
|
|
.status-synced { background: #28a745; }
|
|
.status-error { background: #dc3545; }
|
|
.status-pending { background: #ffc107; }
|
|
@keyframes pulse {
|
|
0% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
100% { opacity: 1; }
|
|
}
|
|
.sync-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 15px;
|
|
margin: 20px 0;
|
|
}
|
|
.stat-card {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
border: 1px solid #dee2e6;
|
|
}
|
|
.stat-value {
|
|
font-size: 2.5rem;
|
|
font-weight: bold;
|
|
color: #007bff;
|
|
}
|
|
.stat-label {
|
|
color: #6c757d;
|
|
font-size: 0.9rem;
|
|
margin-top: 5px;
|
|
}
|
|
.sync-log {
|
|
background: #f8f9fa;
|
|
border-radius: 4px;
|
|
padding: 10px;
|
|
margin: 5px 0;
|
|
font-family: monospace;
|
|
font-size: 0.85rem;
|
|
border-left: 3px solid #dee2e6;
|
|
}
|
|
.log-sync { border-left-color: #007bff; }
|
|
.log-success { border-left-color: #28a745; }
|
|
.log-error { border-left-color: #dc3545; }
|
|
.log-warning { border-left-color: #ffc107; }
|
|
.sync-controls {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin: 15px 0;
|
|
}
|
|
.data-mapping {
|
|
background: #f8f9fa;
|
|
border-radius: 6px;
|
|
padding: 15px;
|
|
margin: 10px 0;
|
|
}
|
|
.mapping-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid #dee2e6;
|
|
}
|
|
.mapping-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.conflict-item {
|
|
background: #fff3cd;
|
|
border: 1px solid #ffeaa7;
|
|
border-radius: 6px;
|
|
padding: 15px;
|
|
margin: 10px 0;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="content">
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h4><i class="fas fa-sync-alt"></i> Data Synchronization</h4>
|
|
<h6>Manage and monitor data synchronization across systems</h6>
|
|
</div>
|
|
<div class="page-btn">
|
|
<button class="btn btn-primary me-2" id="startSync">
|
|
<i class="fas fa-play"></i> Start Sync
|
|
</button>
|
|
<button class="btn btn-warning me-2" id="pauseSync" style="display: none;">
|
|
<i class="fas fa-pause"></i> Pause Sync
|
|
</button>
|
|
<button class="btn btn-info">
|
|
<i class="fas fa-cog"></i> Configure
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sync Dashboard -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="sync-dashboard">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<h5><i class="fas fa-database"></i> Synchronization Status</h5>
|
|
<p>Real-time data synchronization between Hospital Management System and external systems</p>
|
|
<div class="progress-container">
|
|
<div class="sync-progress">
|
|
<div class="progress-bar-animated" id="overallProgress" style="width: 65%"></div>
|
|
<div class="progress-text">65% Complete</div>
|
|
</div>
|
|
<div class="row mt-3">
|
|
<div class="col-md-6">
|
|
<small><strong>Current Operation:</strong> Syncing patient records</small>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<small><strong>ETA:</strong> 15 minutes remaining</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="sync-stats">
|
|
<div class="stat-card">
|
|
<div class="stat-value">1,247</div>
|
|
<div class="stat-label">Records Synced</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">23</div>
|
|
<div class="stat-label">Conflicts</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data Sources -->
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-server"></i> Data Sources</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="data-source">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<h6><span class="source-status status-syncing"></span>Epic EMR System</h6>
|
|
<p>Patient demographics, encounters, and clinical data</p>
|
|
<small class="text-muted">Last sync: 2 minutes ago | Next sync: 8 minutes</small>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="sync-progress">
|
|
<div class="progress-bar-animated" style="width: 75%"></div>
|
|
<div class="progress-text">75%</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<small>847 / 1,129 records</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="data-source">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<h6><span class="source-status status-synced"></span>Laboratory Information System</h6>
|
|
<p>Lab results, test orders, and specimen tracking</p>
|
|
<small class="text-muted">Last sync: 5 minutes ago | Next sync: 5 minutes</small>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="sync-progress">
|
|
<div class="progress-bar-animated" style="width: 100%; background: #28a745;"></div>
|
|
<div class="progress-text">Complete</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<small>234 / 234 records</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="data-source">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<h6><span class="source-status status-error"></span>Pharmacy Management System</h6>
|
|
<p>Medication orders, dispensing, and inventory</p>
|
|
<small class="text-muted">Last sync: 1 hour ago | Error: Connection timeout</small>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="sync-progress">
|
|
<div class="progress-bar-animated" style="width: 30%; background: #dc3545;"></div>
|
|
<div class="progress-text">Error</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<small>89 / 298 records</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="data-source">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<h6><span class="source-status status-pending"></span>Radiology Information System</h6>
|
|
<p>Imaging orders, studies, and reports</p>
|
|
<small class="text-muted">Scheduled for sync in 10 minutes</small>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="sync-progress">
|
|
<div class="progress-bar-animated" style="width: 0%; background: #ffc107;"></div>
|
|
<div class="progress-text">Pending</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<small>0 / 156 records</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data Mapping -->
|
|
<div class="card mt-3">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-exchange-alt"></i> Data Mapping Status</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="data-mapping">
|
|
<h6>Patient Demographics Mapping</h6>
|
|
<div class="mapping-item">
|
|
<span>Patient ID</span>
|
|
<span class="badge bg-success">Mapped</span>
|
|
</div>
|
|
<div class="mapping-item">
|
|
<span>First Name</span>
|
|
<span class="badge bg-success">Mapped</span>
|
|
</div>
|
|
<div class="mapping-item">
|
|
<span>Last Name</span>
|
|
<span class="badge bg-success">Mapped</span>
|
|
</div>
|
|
<div class="mapping-item">
|
|
<span>Date of Birth</span>
|
|
<span class="badge bg-warning">Format Mismatch</span>
|
|
</div>
|
|
<div class="mapping-item">
|
|
<span>Phone Number</span>
|
|
<span class="badge bg-danger">Not Mapped</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="data-mapping">
|
|
<h6>Clinical Data Mapping</h6>
|
|
<div class="mapping-item">
|
|
<span>Diagnosis Codes</span>
|
|
<span class="badge bg-success">Mapped (ICD-10)</span>
|
|
</div>
|
|
<div class="mapping-item">
|
|
<span>Procedure Codes</span>
|
|
<span class="badge bg-success">Mapped (CPT)</span>
|
|
</div>
|
|
<div class="mapping-item">
|
|
<span>Medication Codes</span>
|
|
<span class="badge bg-warning">Partial (RxNorm)</span>
|
|
</div>
|
|
<div class="mapping-item">
|
|
<span>Lab Results</span>
|
|
<span class="badge bg-success">Mapped (LOINC)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<!-- Sync Controls -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-sliders-h"></i> Sync Controls</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="sync-controls">
|
|
<div class="form-group mb-3">
|
|
<label>Sync Frequency</label>
|
|
<select class="form-control">
|
|
<option>Every 10 minutes</option>
|
|
<option>Every 30 minutes</option>
|
|
<option>Every hour</option>
|
|
<option>Every 4 hours</option>
|
|
<option>Daily</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group mb-3">
|
|
<label>Batch Size</label>
|
|
<input type="number" class="form-control" value="100" min="10" max="1000">
|
|
</div>
|
|
<div class="form-check mb-3">
|
|
<input class="form-check-input" type="checkbox" id="autoResolve" checked>
|
|
<label class="form-check-label" for="autoResolve">
|
|
Auto-resolve conflicts
|
|
</label>
|
|
</div>
|
|
<div class="form-check mb-3">
|
|
<input class="form-check-input" type="checkbox" id="notifyErrors" checked>
|
|
<label class="form-check-label" for="notifyErrors">
|
|
Notify on errors
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sync Statistics -->
|
|
<div class="card mt-3">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-chart-bar"></i> Statistics</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="stat-card mb-3">
|
|
<div class="stat-value">98.7%</div>
|
|
<div class="stat-label">Success Rate</div>
|
|
</div>
|
|
<div class="stat-card mb-3">
|
|
<div class="stat-value">2.3</div>
|
|
<div class="stat-label">Avg Sync Time (min)</div>
|
|
</div>
|
|
<div class="stat-card mb-3">
|
|
<div class="stat-value">15,847</div>
|
|
<div class="stat-label">Total Records</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">23</div>
|
|
<div class="stat-label">Active Conflicts</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="card mt-3">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-bolt"></i> Quick Actions</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<button class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-sync"></i> Force Sync All
|
|
</button>
|
|
<button class="btn btn-outline-warning btn-sm">
|
|
<i class="fas fa-exclamation-triangle"></i> Resolve Conflicts
|
|
</button>
|
|
<button class="btn btn-outline-info btn-sm">
|
|
<i class="fas fa-download"></i> Export Log
|
|
</button>
|
|
<button class="btn btn-outline-success btn-sm">
|
|
<i class="fas fa-check"></i> Validate Data
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Conflicts and Issues -->
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-exclamation-triangle"></i> Data Conflicts</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="conflict-item">
|
|
<h6>Patient ID Mismatch</h6>
|
|
<p><strong>Source:</strong> Epic EMR | <strong>Record:</strong> Patient #12345</p>
|
|
<p><strong>Issue:</strong> Different patient ID formats between systems</p>
|
|
<div class="mt-2">
|
|
<button class="btn btn-sm btn-success me-2">Auto-resolve</button>
|
|
<button class="btn btn-sm btn-warning">Manual Review</button>
|
|
</div>
|
|
</div>
|
|
<div class="conflict-item">
|
|
<h6>Duplicate Medication Record</h6>
|
|
<p><strong>Source:</strong> Pharmacy System | <strong>Record:</strong> Medication #789</p>
|
|
<p><strong>Issue:</strong> Same medication exists with different identifiers</p>
|
|
<div class="mt-2">
|
|
<button class="btn btn-sm btn-success me-2">Merge Records</button>
|
|
<button class="btn btn-sm btn-danger">Skip Record</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-list"></i> Sync Log</h5>
|
|
<div class="card-tools">
|
|
<button class="btn btn-sm btn-outline-secondary" id="clearSyncLog">
|
|
<i class="fas fa-trash"></i> Clear
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="syncLog" style="max-height: 300px; overflow-y: auto;">
|
|
<div class="sync-log log-sync">
|
|
[14:30:15] SYNC: Starting synchronization with Epic EMR
|
|
</div>
|
|
<div class="sync-log log-success">
|
|
[14:30:18] SUCCESS: Connected to Epic EMR API
|
|
</div>
|
|
<div class="sync-log log-sync">
|
|
[14:30:20] SYNC: Fetching patient records (batch 1/12)
|
|
</div>
|
|
<div class="sync-log log-success">
|
|
[14:30:25] SUCCESS: Processed 100 patient records
|
|
</div>
|
|
<div class="sync-log log-warning">
|
|
[14:30:28] WARNING: Date format mismatch in record #12345
|
|
</div>
|
|
<div class="sync-log log-sync">
|
|
[14:30:30] SYNC: Fetching patient records (batch 2/12)
|
|
</div>
|
|
<div class="sync-log log-error">
|
|
[14:30:35] ERROR: Failed to process record #67890 - Invalid data format
|
|
</div>
|
|
<div class="sync-log log-sync">
|
|
[14:30:38] SYNC: Retrying failed records
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
$(document).ready(function() {
|
|
var syncRunning = false;
|
|
var syncInterval;
|
|
|
|
// Start sync button
|
|
$('#startSync').click(function() {
|
|
if (!syncRunning) {
|
|
startDataSync();
|
|
}
|
|
});
|
|
|
|
// Pause sync button
|
|
$('#pauseSync').click(function() {
|
|
if (syncRunning) {
|
|
pauseDataSync();
|
|
}
|
|
});
|
|
|
|
function startDataSync() {
|
|
syncRunning = true;
|
|
$('#startSync').hide();
|
|
$('#pauseSync').show();
|
|
|
|
// Update status indicators
|
|
$('.source-status').removeClass('status-synced status-error status-pending').addClass('status-syncing');
|
|
|
|
// Simulate sync progress
|
|
var progress = 65;
|
|
syncInterval = setInterval(function() {
|
|
if (progress < 100) {
|
|
progress += Math.random() * 5;
|
|
if (progress > 100) progress = 100;
|
|
|
|
$('#overallProgress').css('width', progress + '%');
|
|
$('.progress-text').text(Math.round(progress) + '% Complete');
|
|
|
|
// Add random log entries
|
|
var logMessages = [
|
|
'Processing patient demographics...',
|
|
'Syncing clinical data...',
|
|
'Updating medication records...',
|
|
'Validating lab results...',
|
|
'Resolving data conflicts...'
|
|
];
|
|
|
|
if (Math.random() > 0.7) {
|
|
var message = logMessages[Math.floor(Math.random() * logMessages.length)];
|
|
addSyncLogEntry('SYNC', message);
|
|
}
|
|
} else {
|
|
completeSync();
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
function pauseDataSync() {
|
|
syncRunning = false;
|
|
clearInterval(syncInterval);
|
|
$('#startSync').show();
|
|
$('#pauseSync').hide();
|
|
|
|
// Update status indicators
|
|
$('.source-status').removeClass('status-syncing').addClass('status-pending');
|
|
addSyncLogEntry('WARNING', 'Data synchronization paused by user');
|
|
}
|
|
|
|
function completeSync() {
|
|
syncRunning = false;
|
|
clearInterval(syncInterval);
|
|
$('#startSync').show();
|
|
$('#pauseSync').hide();
|
|
|
|
// Update final status
|
|
$('#overallProgress').css('width', '100%');
|
|
$('.progress-text').text('100% Complete');
|
|
$('.source-status').removeClass('status-syncing').addClass('status-synced');
|
|
|
|
addSyncLogEntry('SUCCESS', 'Data synchronization completed successfully');
|
|
}
|
|
|
|
function addSyncLogEntry(level, message) {
|
|
var timestamp = new Date().toLocaleTimeString();
|
|
var logClass = 'log-' + level.toLowerCase();
|
|
var logEntry = '<div class="sync-log ' + logClass + '">[' + timestamp + '] ' + level + ': ' + message + '</div>';
|
|
$('#syncLog').append(logEntry);
|
|
$('#syncLog').scrollTop($('#syncLog')[0].scrollHeight);
|
|
}
|
|
|
|
// Clear sync log
|
|
$('#clearSyncLog').click(function() {
|
|
$('#syncLog').empty();
|
|
});
|
|
|
|
// Quick action buttons
|
|
$('.btn-outline-primary').click(function() {
|
|
startDataSync();
|
|
});
|
|
|
|
$('.btn-outline-warning').click(function() {
|
|
alert('Opening conflict resolution interface...');
|
|
});
|
|
|
|
$('.btn-outline-info').click(function() {
|
|
alert('Exporting synchronization log...');
|
|
});
|
|
|
|
$('.btn-outline-success').click(function() {
|
|
alert('Starting data validation process...');
|
|
});
|
|
|
|
// Conflict resolution buttons
|
|
$('.btn-success').click(function() {
|
|
$(this).closest('.conflict-item').fadeOut();
|
|
});
|
|
|
|
$('.btn-warning, .btn-danger').click(function() {
|
|
alert('Opening manual review interface...');
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|