Marwan Alwali 0a037d3d9d update
2025-09-01 11:26:11 +03:00

1513 lines
56 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'base.html' %}
{% load static %}
{% block title %}{% if measurement %}Edit{% else %}Create{% endif %} Quality Measurement{% endblock %}
{% block css %}
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
<style>
.form-header {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.form-layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.form-main {
display: flex;
flex-direction: column;
gap: 2rem;
}
.form-sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.section-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
overflow: hidden;
}
.section-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
font-weight: 600;
color: #495057;
display: flex;
align-items: center;
gap: 0.5rem;
}
.section-content {
padding: 1.5rem;
}
.form-tabs {
border-bottom: 1px solid #dee2e6;
margin-bottom: 1.5rem;
}
.nav-tabs .nav-link {
border: none;
border-bottom: 2px solid transparent;
color: #6c757d;
font-weight: 500;
padding: 1rem 1.5rem;
}
.nav-tabs .nav-link.active {
color: #28a745;
border-bottom-color: #28a745;
background: none;
}
.nav-tabs .nav-link:hover {
color: #28a745;
border-color: transparent;
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
font-weight: 600;
color: #495057;
margin-bottom: 0.5rem;
}
.form-label.required::after {
content: ' *';
color: #dc3545;
}
.form-control, .form-select {
border: 1px solid #ced4da;
border-radius: 0.375rem;
padding: 0.75rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.form-control:focus, .form-select:focus {
border-color: #28a745;
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
}
.input-group-text {
background: #f8f9fa;
border: 1px solid #ced4da;
color: #6c757d;
}
.form-text {
font-size: 0.875rem;
color: #6c757d;
margin-top: 0.25rem;
}
.category-selector {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 0.75rem;
margin-bottom: 1rem;
}
.category-option {
position: relative;
}
.category-option input[type="radio"] {
position: absolute;
opacity: 0;
}
.category-label {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
padding: 1rem;
border: 2px solid #dee2e6;
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.category-option input[type="radio"]:checked + .category-label {
border-color: #28a745;
background: #e8f5e8;
color: #2e7d32;
}
.category-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
color: white;
margin-bottom: 0.5rem;
}
.category-clinical { background: #1976d2; }
.category-operational { background: #2e7d32; }
.category-financial { background: #f57c00; }
.category-safety { background: #d32f2f; }
.category-satisfaction { background: #7b1fa2; }
.category-name {
font-weight: 600;
font-size: 0.875rem;
}
.frequency-selector {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
}
.frequency-option {
position: relative;
}
.frequency-option input[type="radio"] {
position: absolute;
opacity: 0;
}
.frequency-label {
display: block;
padding: 0.75rem;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
cursor: pointer;
transition: all 0.2s;
text-align: center;
font-size: 0.875rem;
font-weight: 500;
}
.frequency-option input[type="radio"]:checked + .frequency-label {
border-color: #28a745;
background: #28a745;
color: white;
}
.target-settings {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.target-type-selector {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
.target-type {
flex: 1;
position: relative;
}
.target-type input[type="radio"] {
position: absolute;
opacity: 0;
}
.target-type-label {
display: block;
padding: 1rem;
border: 2px solid #dee2e6;
border-radius: 0.375rem;
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.target-type input[type="radio"]:checked + .target-type-label {
border-color: #28a745;
background: #e8f5e8;
}
.target-direction {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.direction-option {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
cursor: pointer;
transition: all 0.2s;
}
.direction-option.active {
border-color: #28a745;
background: #e8f5e8;
color: #2e7d32;
}
.calculation-method {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.method-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.method-option {
position: relative;
}
.method-option input[type="radio"] {
position: absolute;
opacity: 0;
}
.method-label {
display: block;
padding: 1rem;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
cursor: pointer;
transition: all 0.2s;
}
.method-option input[type="radio"]:checked + .method-label {
border-color: #28a745;
background: #e8f5e8;
}
.method-title {
font-weight: 600;
margin-bottom: 0.5rem;
}
.method-description {
font-size: 0.875rem;
color: #6c757d;
}
.alert-configuration {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.alert-rules {
display: flex;
flex-direction: column;
gap: 1rem;
}
.alert-rule {
display: grid;
grid-template-columns: 150px 100px 1fr auto;
gap: 0.5rem;
align-items: center;
padding: 0.75rem;
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
}
.team-assignment {
background: #e3f2fd;
border: 1px solid #bbdefb;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.team-members {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
}
.team-member {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
font-size: 0.875rem;
}
.member-avatar {
width: 24px;
height: 24px;
border-radius: 50%;
background: #28a745;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 600;
}
.remove-member {
width: 20px;
height: 20px;
border: none;
background: #dc3545;
color: white;
border-radius: 50%;
cursor: pointer;
font-size: 0.75rem;
display: flex;
align-items: center;
justify-content: center;
}
.form-actions {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
display: flex;
justify-content: between;
align-items: center;
gap: 1rem;
}
.action-group {
display: flex;
gap: 0.5rem;
}
.auto-save-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: #6c757d;
}
.save-status {
width: 8px;
height: 8px;
border-radius: 50%;
background: #6c757d;
}
.save-status.saving {
background: #ffc107;
animation: pulse 1s infinite;
}
.save-status.saved {
background: #28a745;
}
.save-status.error {
background: #dc3545;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.preview-section {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.preview-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
position: relative;
}
.preview-card::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: #28a745;
border-radius: 0.5rem 0 0 0.5rem;
}
.validation-errors {
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1.5rem;
}
.error-list {
list-style: none;
padding: 0;
margin: 0;
}
.error-item {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
font-size: 0.875rem;
color: #721c24;
}
.error-icon {
color: #dc3545;
}
@media (max-width: 1200px) {
.form-layout {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.form-sidebar {
order: -1;
}
}
@media (max-width: 768px) {
.form-header {
padding: 1.5rem;
}
.form-grid {
grid-template-columns: 1fr;
}
.category-selector {
grid-template-columns: 1fr;
}
.frequency-selector {
grid-template-columns: repeat(2, 1fr);
}
.target-type-selector {
flex-direction: column;
}
.method-options {
grid-template-columns: 1fr;
}
.alert-rule {
grid-template-columns: 1fr;
}
.form-actions {
flex-direction: column;
align-items: stretch;
}
.action-group {
justify-content: center;
}
}
@media print {
.form-sidebar, .form-actions {
display: none !important;
}
.form-layout {
grid-template-columns: 1fr;
}
.section-header {
background: none;
border-bottom: 2px solid #000;
color: #000;
}
}
</style>
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<!-- Page Header -->
<div class="d-flex align-items-center mb-3">
<div>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'quality:dashboard' %}">Quality</a></li>
<li class="breadcrumb-item"><a href="{% url 'quality:measurement_list' %}">Measurements</a></li>
<li class="breadcrumb-item active">{% if measurement %}Edit{% else %}Create{% endif %}</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-{% if measurement %}edit{% else %}plus{% endif %} me-2"></i>
{% if measurement %}Edit Measurement{% else %}Create Quality Measurement{% endif %}
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'quality:measurement_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to List
</a>
</div>
</div>
<!-- Form Header -->
<div class="form-header">
<div class="row align-items-center">
<div class="col-md-8">
<h2 class="mb-2">
{% if measurement %}
Edit "{{ measurement.name }}"
{% else %}
Create New Quality Measurement
{% endif %}
</h2>
<p class="mb-0">
{% if measurement %}
Update the measurement configuration and settings below.
{% else %}
Define a new quality measurement to track performance metrics and targets.
{% endif %}
</p>
</div>
<div class="col-md-4 text-md-end">
<div class="auto-save-indicator">
<div class="save-status" id="save-status"></div>
<span id="save-text">Ready</span>
</div>
</div>
</div>
</div>
<!-- Validation Errors -->
{% if form.errors %}
<div class="validation-errors">
<h6 class="mb-3">
<i class="fas fa-exclamation-triangle me-2"></i>Please correct the following errors:
</h6>
<ul class="error-list">
{% for field, errors in form.errors.items %}
{% for error in errors %}
<li class="error-item">
<i class="fas fa-times-circle error-icon"></i>
<strong>{{ field|title }}:</strong> {{ error }}
</li>
{% endfor %}
{% endfor %}
</ul>
</div>
{% endif %}
<form method="post" id="measurement-form" novalidate>
{% csrf_token %}
<div class="form-layout">
<!-- Main Form Content -->
<div class="form-main">
<!-- Basic Information -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-info-circle"></i>
Basic Information
</div>
<div class="section-content">
<div class="form-grid">
<div class="form-group">
<label class="form-label required">Measurement Name</label>
<input type="text" class="form-control" name="name"
value="{{ form.name.value|default:'' }}"
placeholder="Enter measurement name" required>
<div class="form-text">A clear, descriptive name for this measurement</div>
</div>
<div class="form-group">
<label class="form-label">Measurement ID</label>
<input type="text" class="form-control" name="measurement_id"
value="{{ form.measurement_id.value|default:'' }}"
placeholder="Auto-generated if empty">
<div class="form-text">Unique identifier for this measurement</div>
</div>
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea class="form-control" name="description" rows="3"
placeholder="Describe what this measurement tracks and why it's important">{{ form.description.value|default:'' }}</textarea>
<div class="form-text">Detailed description of the measurement purpose and methodology</div>
</div>
<div class="form-grid">
<div class="form-group">
<label class="form-label required">Department</label>
<select class="form-select" name="department" required>
<option value="">Select Department</option>
{% for dept in departments %}
<option value="{{ dept.id }}" {% if form.department.value == dept.id %}selected{% endif %}>
{{ dept.name }}
</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label class="form-label">Owner</label>
<select class="form-select" name="owner">
<option value="">Select Owner</option>
{% for user in users %}
<option value="{{ user.id }}" {% if form.owner.value == user.id %}selected{% endif %}>
{{ user.get_full_name }}
</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
<!-- Category Selection -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-tags"></i>
Category & Classification
</div>
<div class="section-content">
<div class="form-group">
<label class="form-label required">Measurement Category</label>
<div class="category-selector">
<div class="category-option">
<input type="radio" name="category" value="clinical" id="cat-clinical"
{% if form.category.value == 'clinical' %}checked{% endif %} required>
<label for="cat-clinical" class="category-label">
<div class="category-icon category-clinical">
<i class="fas fa-user-md"></i>
</div>
<div class="category-name">Clinical Quality</div>
</label>
</div>
<div class="category-option">
<input type="radio" name="category" value="operational" id="cat-operational"
{% if form.category.value == 'operational' %}checked{% endif %}>
<label for="cat-operational" class="category-label">
<div class="category-icon category-operational">
<i class="fas fa-cogs"></i>
</div>
<div class="category-name">Operational</div>
</label>
</div>
<div class="category-option">
<input type="radio" name="category" value="financial" id="cat-financial"
{% if form.category.value == 'financial' %}checked{% endif %}>
<label for="cat-financial" class="category-label">
<div class="category-icon category-financial">
<i class="fas fa-dollar-sign"></i>
</div>
<div class="category-name">Financial</div>
</label>
</div>
<div class="category-option">
<input type="radio" name="category" value="safety" id="cat-safety"
{% if form.category.value == 'safety' %}checked{% endif %}>
<label for="cat-safety" class="category-label">
<div class="category-icon category-safety">
<i class="fas fa-shield-alt"></i>
</div>
<div class="category-name">Safety</div>
</label>
</div>
<div class="category-option">
<input type="radio" name="category" value="satisfaction" id="cat-satisfaction"
{% if form.category.value == 'satisfaction' %}checked{% endif %}>
<label for="cat-satisfaction" class="category-label">
<div class="category-icon category-satisfaction">
<i class="fas fa-heart"></i>
</div>
<div class="category-name">Satisfaction</div>
</label>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label required">Measurement Frequency</label>
<div class="frequency-selector">
<div class="frequency-option">
<input type="radio" name="frequency" value="daily" id="freq-daily"
{% if form.frequency.value == 'daily' %}checked{% endif %} required>
<label for="freq-daily" class="frequency-label">Daily</label>
</div>
<div class="frequency-option">
<input type="radio" name="frequency" value="weekly" id="freq-weekly"
{% if form.frequency.value == 'weekly' %}checked{% endif %}>
<label for="freq-weekly" class="frequency-label">Weekly</label>
</div>
<div class="frequency-option">
<input type="radio" name="frequency" value="monthly" id="freq-monthly"
{% if form.frequency.value == 'monthly' %}checked{% endif %}>
<label for="freq-monthly" class="frequency-label">Monthly</label>
</div>
<div class="frequency-option">
<input type="radio" name="frequency" value="quarterly" id="freq-quarterly"
{% if form.frequency.value == 'quarterly' %}checked{% endif %}>
<label for="freq-quarterly" class="frequency-label">Quarterly</label>
</div>
<div class="frequency-option">
<input type="radio" name="frequency" value="annually" id="freq-annually"
{% if form.frequency.value == 'annually' %}checked{% endif %}>
<label for="freq-annually" class="frequency-label">Annually</label>
</div>
</div>
</div>
</div>
</div>
<!-- Measurement Configuration -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-sliders-h"></i>
Measurement Configuration
</div>
<div class="section-content">
<div class="form-grid">
<div class="form-group">
<label class="form-label">Unit of Measure</label>
<input type="text" class="form-control" name="unit_of_measure"
value="{{ form.unit_of_measure.value|default:'' }}"
placeholder="e.g., %, minutes, count, ratio">
<div class="form-text">The unit used to express measurement values</div>
</div>
<div class="form-group">
<label class="form-label">Data Source</label>
<select class="form-select" name="data_source">
<option value="">Select Data Source</option>
<option value="manual" {% if form.data_source.value == 'manual' %}selected{% endif %}>Manual Entry</option>
<option value="ehr" {% if form.data_source.value == 'ehr' %}selected{% endif %}>Electronic Health Records</option>
<option value="lab" {% if form.data_source.value == 'lab' %}selected{% endif %}>Laboratory System</option>
<option value="billing" {% if form.data_source.value == 'billing' %}selected{% endif %}>Billing System</option>
<option value="survey" {% if form.data_source.value == 'survey' %}selected{% endif %}>Patient Surveys</option>
<option value="external" {% if form.data_source.value == 'external' %}selected{% endif %}>External System</option>
</select>
</div>
</div>
<div class="calculation-method">
<h6 class="mb-3">
<i class="fas fa-calculator me-2"></i>Calculation Method
</h6>
<div class="method-options">
<div class="method-option">
<input type="radio" name="calculation_method" value="direct" id="calc-direct"
{% if form.calculation_method.value == 'direct' or not form.calculation_method.value %}checked{% endif %}>
<label for="calc-direct" class="method-label">
<div class="method-title">Direct Measurement</div>
<div class="method-description">Values are entered directly without calculation</div>
</label>
</div>
<div class="method-option">
<input type="radio" name="calculation_method" value="percentage" id="calc-percentage"
{% if form.calculation_method.value == 'percentage' %}checked{% endif %}>
<label for="calc-percentage" class="method-label">
<div class="method-title">Percentage</div>
<div class="method-description">Calculate as (numerator/denominator) × 100</div>
</label>
</div>
<div class="method-option">
<input type="radio" name="calculation_method" value="rate" id="calc-rate"
{% if form.calculation_method.value == 'rate' %}checked{% endif %}>
<label for="calc-rate" class="method-label">
<div class="method-title">Rate</div>
<div class="method-description">Calculate as events per time period</div>
</label>
</div>
<div class="method-option">
<input type="radio" name="calculation_method" value="average" id="calc-average"
{% if form.calculation_method.value == 'average' %}checked{% endif %}>
<label for="calc-average" class="method-label">
<div class="method-title">Average</div>
<div class="method-description">Calculate mean of multiple values</div>
</label>
</div>
</div>
</div>
</div>
</div>
<!-- Target Settings -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-bullseye"></i>
Target Settings
</div>
<div class="section-content">
<div class="target-settings">
<h6 class="mb-3">
<i class="fas fa-target me-2"></i>Target Configuration
</h6>
<div class="target-type-selector">
<div class="target-type">
<input type="radio" name="target_type" value="fixed" id="target-fixed"
{% if form.target_type.value == 'fixed' or not form.target_type.value %}checked{% endif %}>
<label for="target-fixed" class="target-type-label">
<i class="fas fa-bullseye mb-2"></i>
<div>Fixed Target</div>
<small class="text-muted">Set a specific target value</small>
</label>
</div>
<div class="target-type">
<input type="radio" name="target_type" value="range" id="target-range"
{% if form.target_type.value == 'range' %}checked{% endif %}>
<label for="target-range" class="target-type-label">
<i class="fas fa-arrows-alt-h mb-2"></i>
<div>Target Range</div>
<small class="text-muted">Set minimum and maximum values</small>
</label>
</div>
</div>
<div class="form-grid" id="target-inputs">
<div class="form-group">
<label class="form-label">Target Value</label>
<input type="number" class="form-control" name="target_value"
value="{{ form.target_value.value|default:'' }}"
placeholder="Enter target value" step="0.01">
</div>
<div class="form-group">
<label class="form-label">Benchmark Value</label>
<input type="number" class="form-control" name="benchmark"
value="{{ form.benchmark.value|default:'' }}"
placeholder="Industry benchmark" step="0.01">
</div>
</div>
<div class="target-direction">
<label class="form-label">Target Direction:</label>
<div class="direction-option" onclick="setDirection('higher')">
<i class="fas fa-arrow-up"></i>
<span>Higher is Better</span>
</div>
<div class="direction-option" onclick="setDirection('lower')">
<i class="fas fa-arrow-down"></i>
<span>Lower is Better</span>
</div>
<div class="direction-option active" onclick="setDirection('exact')">
<i class="fas fa-bullseye"></i>
<span>Exact Target</span>
</div>
</div>
<input type="hidden" name="target_direction" value="exact" id="target-direction-input">
</div>
</div>
</div>
<!-- Alert Configuration -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-bell"></i>
Alert Configuration
</div>
<div class="section-content">
<div class="alert-configuration">
<h6 class="mb-3">
<i class="fas fa-exclamation-triangle me-2"></i>Alert Rules
</h6>
<div class="alert-rules" id="alert-rules">
<div class="alert-rule">
<select class="form-select" name="alert_condition_1">
<option value="">Select Condition</option>
<option value="below_target">Below Target</option>
<option value="above_target">Above Target</option>
<option value="no_data">No Data Recorded</option>
<option value="trend_down">Declining Trend</option>
<option value="trend_up">Improving Trend</option>
</select>
<select class="form-select" name="alert_severity_1">
<option value="info">Info</option>
<option value="warning">Warning</option>
<option value="critical">Critical</option>
</select>
<input type="text" class="form-control" name="alert_message_1"
placeholder="Alert message">
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeAlertRule(this)">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="text-center mt-3">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addAlertRule()">
<i class="fas fa-plus me-1"></i>Add Alert Rule
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="form-sidebar">
<!-- Form Preview -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-eye"></i>
Preview
</div>
<div class="section-content">
<div class="preview-section">
<div class="preview-card" id="measurement-preview">
<h6 id="preview-name">Measurement Name</h6>
<p class="text-muted mb-2" id="preview-description">Description will appear here</p>
<div class="mb-2">
<span class="badge bg-primary" id="preview-category">Category</span>
<span class="badge bg-secondary" id="preview-frequency">Frequency</span>
</div>
<div class="small text-muted">
<div><strong>Department:</strong> <span id="preview-department">Not selected</span></div>
<div><strong>Target:</strong> <span id="preview-target">Not set</span></div>
<div><strong>Unit:</strong> <span id="preview-unit">Not specified</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- Team Assignment -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-users"></i>
Team Assignment
</div>
<div class="section-content">
<div class="team-assignment">
<h6 class="mb-3">
<i class="fas fa-user-friends me-2"></i>Assigned Team Members
</h6>
<div class="team-members" id="team-members">
<!-- Team members will be added here -->
</div>
<div class="form-group">
<label class="form-label">Add Team Member</label>
<select class="form-select" id="team-member-select">
<option value="">Select team member</option>
{% for user in users %}
<option value="{{ user.id }}" data-name="{{ user.get_full_name }}">
{{ user.get_full_name }} - {{ user.email }}
</option>
{% endfor %}
</select>
</div>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addTeamMember()">
<i class="fas fa-plus me-1"></i>Add Member
</button>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-bolt"></i>
Quick Actions
</div>
<div class="section-content">
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-info btn-sm" onclick="loadTemplate()">
<i class="fas fa-file-import me-1"></i>Load Template
</button>
<button type="button" class="btn btn-outline-success btn-sm" onclick="saveTemplate()">
<i class="fas fa-save me-1"></i>Save as Template
</button>
<button type="button" class="btn btn-outline-warning btn-sm" onclick="validateForm()">
<i class="fas fa-check-circle me-1"></i>Validate Form
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="resetForm()">
<i class="fas fa-undo me-1"></i>Reset Form
</button>
</div>
</div>
</div>
<!-- Help & Tips -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-question-circle"></i>
Help & Tips
</div>
<div class="section-content">
<div class="small">
<h6>Creating Effective Measurements:</h6>
<ul class="list-unstyled">
<li class="mb-2">
<i class="fas fa-lightbulb text-warning me-2"></i>
Use clear, specific names that describe what you're measuring
</li>
<li class="mb-2">
<i class="fas fa-lightbulb text-warning me-2"></i>
Set realistic but challenging targets based on historical data
</li>
<li class="mb-2">
<i class="fas fa-lightbulb text-warning me-2"></i>
Choose appropriate measurement frequency for your needs
</li>
<li class="mb-2">
<i class="fas fa-lightbulb text-warning me-2"></i>
Configure alerts to notify you of important changes
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="form-actions">
<div class="auto-save-indicator">
<div class="save-status" id="save-status-bottom"></div>
<span id="save-text-bottom">Ready to save</span>
</div>
<div class="action-group">
<a href="{% url 'quality:measurement_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i>Cancel
</a>
<button type="button" class="btn btn-outline-info" onclick="saveDraft()">
<i class="fas fa-save me-1"></i>Save Draft
</button>
<button type="submit" class="btn btn-success">
<i class="fas fa-check me-1"></i>
{% if measurement %}Update Measurement{% else %}Create Measurement{% endif %}
</button>
</div>
</div>
</form>
</div>
<!-- Template Selection Modal -->
<div class="modal fade" id="templateModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-file-import me-2"></i>Load Measurement Template
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<div class="template-option" onclick="loadMeasurementTemplate('clinical')">
<i class="fas fa-user-md fa-2x mb-2 text-primary"></i>
<h6>Clinical Quality</h6>
<p class="small text-muted">Patient care and clinical outcome measurements</p>
</div>
</div>
<div class="col-md-6">
<div class="template-option" onclick="loadMeasurementTemplate('operational')">
<i class="fas fa-cogs fa-2x mb-2 text-success"></i>
<h6>Operational Efficiency</h6>
<p class="small text-muted">Process efficiency and operational metrics</p>
</div>
</div>
<div class="col-md-6">
<div class="template-option" onclick="loadMeasurementTemplate('safety')">
<i class="fas fa-shield-alt fa-2x mb-2 text-danger"></i>
<h6>Patient Safety</h6>
<p class="small text-muted">Safety incidents and prevention measures</p>
</div>
</div>
<div class="col-md-6">
<div class="template-option" onclick="loadMeasurementTemplate('satisfaction')">
<i class="fas fa-heart fa-2x mb-2 text-warning"></i>
<h6>Patient Satisfaction</h6>
<p class="small text-muted">Patient experience and satisfaction surveys</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>
<script>
let teamMembers = [];
let autoSaveTimer = null;
$(document).ready(function() {
// Initialize Select2
$('.form-select').select2({
theme: 'bootstrap-5'
});
// Auto-save functionality
$('#measurement-form input, #measurement-form select, #measurement-form textarea').on('change keyup', function() {
clearTimeout(autoSaveTimer);
updateSaveStatus('saving');
autoSaveTimer = setTimeout(function() {
saveDraft();
}, 2000);
updatePreview();
});
// Initialize preview
updatePreview();
});
function updateSaveStatus(status) {
const statusElement = document.getElementById('save-status');
const textElement = document.getElementById('save-text');
const statusElementBottom = document.getElementById('save-status-bottom');
const textElementBottom = document.getElementById('save-text-bottom');
statusElement.className = `save-status ${status}`;
statusElementBottom.className = `save-status ${status}`;
switch(status) {
case 'saving':
textElement.textContent = 'Saving...';
textElementBottom.textContent = 'Saving...';
break;
case 'saved':
textElement.textContent = 'Saved';
textElementBottom.textContent = 'Saved';
break;
case 'error':
textElement.textContent = 'Error saving';
textElementBottom.textContent = 'Error saving';
break;
default:
textElement.textContent = 'Ready';
textElementBottom.textContent = 'Ready to save';
}
}
function updatePreview() {
const name = document.querySelector('[name="name"]').value || 'Measurement Name';
const description = document.querySelector('[name="description"]').value || 'Description will appear here';
const category = document.querySelector('[name="category"]:checked')?.value || 'Category';
const frequency = document.querySelector('[name="frequency"]:checked')?.value || 'Frequency';
const department = document.querySelector('[name="department"] option:checked')?.textContent || 'Not selected';
const target = document.querySelector('[name="target_value"]').value || 'Not set';
const unit = document.querySelector('[name="unit_of_measure"]').value || 'Not specified';
document.getElementById('preview-name').textContent = name;
document.getElementById('preview-description').textContent = description;
document.getElementById('preview-category').textContent = category.charAt(0).toUpperCase() + category.slice(1);
document.getElementById('preview-frequency').textContent = frequency.charAt(0).toUpperCase() + frequency.slice(1);
document.getElementById('preview-department').textContent = department;
document.getElementById('preview-target').textContent = target + (unit !== 'Not specified' ? ' ' + unit : '');
document.getElementById('preview-unit').textContent = unit;
}
function setDirection(direction) {
document.querySelectorAll('.direction-option').forEach(el => el.classList.remove('active'));
event.currentTarget.classList.add('active');
document.getElementById('target-direction-input').value = direction;
}
function addAlertRule() {
const rulesContainer = document.getElementById('alert-rules');
const ruleCount = rulesContainer.children.length + 1;
const ruleHtml = `
<div class="alert-rule">
<select class="form-select" name="alert_condition_${ruleCount}">
<option value="">Select Condition</option>
<option value="below_target">Below Target</option>
<option value="above_target">Above Target</option>
<option value="no_data">No Data Recorded</option>
<option value="trend_down">Declining Trend</option>
<option value="trend_up">Improving Trend</option>
</select>
<select class="form-select" name="alert_severity_${ruleCount}">
<option value="info">Info</option>
<option value="warning">Warning</option>
<option value="critical">Critical</option>
</select>
<input type="text" class="form-control" name="alert_message_${ruleCount}"
placeholder="Alert message">
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeAlertRule(this)">
<i class="fas fa-trash"></i>
</button>
</div>
`;
rulesContainer.insertAdjacentHTML('beforeend', ruleHtml);
}
function removeAlertRule(button) {
button.closest('.alert-rule').remove();
}
function addTeamMember() {
const select = document.getElementById('team-member-select');
const selectedOption = select.options[select.selectedIndex];
if (!selectedOption.value) {
showAlert('Please select a team member', 'warning');
return;
}
const memberId = selectedOption.value;
const memberName = selectedOption.dataset.name;
// Check if member is already added
if (teamMembers.includes(memberId)) {
showAlert('Team member already added', 'warning');
return;
}
teamMembers.push(memberId);
const memberHtml = `
<div class="team-member" data-member-id="${memberId}">
<div class="member-avatar">
${memberName.split(' ').map(n => n[0]).join('')}
</div>
<span>${memberName}</span>
<button type="button" class="remove-member" onclick="removeTeamMember('${memberId}')">
<i class="fas fa-times"></i>
</button>
<input type="hidden" name="team_members" value="${memberId}">
</div>
`;
document.getElementById('team-members').insertAdjacentHTML('beforeend', memberHtml);
select.selectedIndex = 0;
}
function removeTeamMember(memberId) {
teamMembers = teamMembers.filter(id => id !== memberId);
document.querySelector(`[data-member-id="${memberId}"]`).remove();
}
function saveDraft() {
const formData = new FormData(document.getElementById('measurement-form'));
formData.append('is_draft', 'true');
fetch(window.location.href, {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
updateSaveStatus('saved');
} else {
updateSaveStatus('error');
}
})
.catch(error => {
updateSaveStatus('error');
});
}
function loadTemplate() {
new bootstrap.Modal(document.getElementById('templateModal')).show();
}
function loadMeasurementTemplate(type) {
const templates = {
clinical: {
name: 'Patient Readmission Rate',
description: 'Percentage of patients readmitted within 30 days of discharge',
category: 'clinical',
frequency: 'monthly',
unit_of_measure: '%',
target_value: '10',
calculation_method: 'percentage'
},
operational: {
name: 'Average Length of Stay',
description: 'Average number of days patients stay in the hospital',
category: 'operational',
frequency: 'monthly',
unit_of_measure: 'days',
target_value: '4.5',
calculation_method: 'average'
},
safety: {
name: 'Hospital-Acquired Infection Rate',
description: 'Rate of infections acquired during hospital stay',
category: 'safety',
frequency: 'monthly',
unit_of_measure: 'per 1000 patient days',
target_value: '2',
calculation_method: 'rate'
},
satisfaction: {
name: 'Patient Satisfaction Score',
description: 'Overall patient satisfaction rating from surveys',
category: 'satisfaction',
frequency: 'monthly',
unit_of_measure: 'score (1-10)',
target_value: '8.5',
calculation_method: 'average'
}
};
const template = templates[type];
if (template) {
Object.keys(template).forEach(key => {
const field = document.querySelector(`[name="${key}"]`);
if (field) {
if (field.type === 'radio') {
document.querySelector(`[name="${key}"][value="${template[key]}"]`).checked = true;
} else {
field.value = template[key];
}
}
});
updatePreview();
showAlert(`${type.charAt(0).toUpperCase() + type.slice(1)} template loaded`, 'success');
}
bootstrap.Modal.getInstance(document.getElementById('templateModal')).hide();
}
function saveTemplate() {
const templateName = prompt('Enter template name:');
if (templateName) {
// In real implementation, this would save the current form as a template
showAlert('Template saved successfully', 'success');
}
}
function validateForm() {
const form = document.getElementById('measurement-form');
const requiredFields = form.querySelectorAll('[required]');
let isValid = true;
let errors = [];
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
errors.push(`${field.name} is required`);
field.classList.add('is-invalid');
} else {
field.classList.remove('is-invalid');
}
});
if (isValid) {
showAlert('Form validation passed', 'success');
} else {
showAlert(`Validation failed: ${errors.join(', ')}`, 'danger');
}
}
function resetForm() {
if (confirm('Are you sure you want to reset the form? All unsaved changes will be lost.')) {
document.getElementById('measurement-form').reset();
teamMembers = [];
document.getElementById('team-members').innerHTML = '';
updatePreview();
showAlert('Form reset successfully', 'info');
}
}
function showAlert(message, type) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 1060; min-width: 300px;';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alertDiv);
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
</script>
{% endblock %}