2025-08-12 13:33:25 +03:00

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 %}