1389 lines
50 KiB
HTML
1389 lines
50 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% if finding %}Edit Finding{% else %}New Finding{% endif %} - Quality Management{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
|
|
<link href="{% static 'assets/plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
|
|
<style>
|
|
.form-header {
|
|
background: linear-gradient(135deg, #dc3545 0%, #c82333 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;
|
|
}
|
|
|
|
.form-section {
|
|
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-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.form-row.single {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.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 #dee2e6;
|
|
border-radius: 0.375rem;
|
|
padding: 0.75rem;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.form-control:focus, .form-select:focus {
|
|
border-color: #dc3545;
|
|
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
|
|
}
|
|
|
|
.form-text {
|
|
font-size: 0.875rem;
|
|
color: #6c757d;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.severity-selector {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.severity-option {
|
|
padding: 1rem;
|
|
border: 2px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
background: white;
|
|
}
|
|
|
|
.severity-option:hover {
|
|
border-color: #dc3545;
|
|
}
|
|
|
|
.severity-option.selected {
|
|
border-color: #dc3545;
|
|
background: #fff5f5;
|
|
color: #dc3545;
|
|
}
|
|
|
|
.severity-low.selected { border-color: #28a745; background: #e8f5e8; color: #2e7d32; }
|
|
.severity-medium.selected { border-color: #ffc107; background: #fff3e0; color: #f57c00; }
|
|
.severity-high.selected { border-color: #fd7e14; background: #ffebee; color: #d32f2f; }
|
|
.severity-critical.selected { border-color: #dc3545; background: #f8d7da; color: #721c24; }
|
|
|
|
.category-selector {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.category-option {
|
|
padding: 0.75rem;
|
|
border: 2px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
background: white;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.category-option:hover {
|
|
border-color: #dc3545;
|
|
}
|
|
|
|
.category-option.selected {
|
|
border-color: #dc3545;
|
|
background: #fff5f5;
|
|
color: #dc3545;
|
|
}
|
|
|
|
.category-clinical.selected { border-color: #1976d2; background: #e3f2fd; color: #1976d2; }
|
|
.category-safety.selected { border-color: #d32f2f; background: #ffebee; color: #d32f2f; }
|
|
.category-compliance.selected { border-color: #7b1fa2; background: #f3e5f5; color: #7b1fa2; }
|
|
.category-process.selected { border-color: #2e7d32; background: #e8f5e8; color: #2e7d32; }
|
|
.category-documentation.selected { border-color: #f57c00; background: #fff3e0; color: #f57c00; }
|
|
|
|
.location-selector {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.location-option {
|
|
padding: 0.75rem;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
background: white;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.location-option:hover {
|
|
border-color: #dc3545;
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
.location-option.selected {
|
|
border-color: #dc3545;
|
|
background: #fff5f5;
|
|
color: #dc3545;
|
|
}
|
|
|
|
.risk-assessment {
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
padding: 1rem;
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
.risk-factors {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 0.5rem;
|
|
margin-top: 0.75rem;
|
|
}
|
|
|
|
.risk-factor {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.risk-factor input[type="checkbox"] {
|
|
margin: 0;
|
|
}
|
|
|
|
.risk-factor label {
|
|
margin: 0;
|
|
font-size: 0.875rem;
|
|
color: #495057;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.evidence-upload {
|
|
border: 2px dashed #dee2e6;
|
|
border-radius: 0.375rem;
|
|
padding: 2rem;
|
|
text-align: center;
|
|
transition: all 0.2s;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.evidence-upload:hover {
|
|
border-color: #dc3545;
|
|
background: #fff5f5;
|
|
}
|
|
|
|
.evidence-upload.dragover {
|
|
border-color: #dc3545;
|
|
background: #fff5f5;
|
|
}
|
|
|
|
.upload-icon {
|
|
font-size: 2rem;
|
|
color: #dc3545;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.upload-text {
|
|
color: #495057;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.upload-hint {
|
|
font-size: 0.875rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.uploaded-files {
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.file-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 0.75rem;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
margin-bottom: 0.5rem;
|
|
background: white;
|
|
}
|
|
|
|
.file-icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 0.25rem;
|
|
background: #dc3545;
|
|
color: white;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.file-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.file-name {
|
|
font-weight: 600;
|
|
color: #495057;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.file-meta {
|
|
font-size: 0.75rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.file-remove {
|
|
width: 24px;
|
|
height: 24px;
|
|
border: none;
|
|
background: #dc3545;
|
|
color: white;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.btn-group {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.auto-save-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
color: #6c757d;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.auto-save-indicator.saving {
|
|
color: #ffc107;
|
|
}
|
|
|
|
.auto-save-indicator.saved {
|
|
color: #28a745;
|
|
}
|
|
|
|
.validation-errors {
|
|
background: #f8d7da;
|
|
border: 1px solid #f5c6cb;
|
|
border-radius: 0.375rem;
|
|
padding: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.validation-errors ul {
|
|
margin: 0;
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
.validation-errors li {
|
|
color: #721c24;
|
|
}
|
|
|
|
.impact-assessment {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.impact-item {
|
|
text-align: center;
|
|
padding: 1rem;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
background: white;
|
|
}
|
|
|
|
.impact-icon {
|
|
font-size: 1.5rem;
|
|
color: #dc3545;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.impact-label {
|
|
font-size: 0.875rem;
|
|
color: #495057;
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.impact-rating {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.rating-star {
|
|
width: 16px;
|
|
height: 16px;
|
|
color: #dee2e6;
|
|
cursor: pointer;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
.rating-star.active {
|
|
color: #ffc107;
|
|
}
|
|
|
|
.timeline-preview {
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.375rem;
|
|
padding: 1rem;
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
.timeline-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.timeline-item:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.timeline-dot {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
background: #dee2e6;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.timeline-dot.active {
|
|
background: #dc3545;
|
|
}
|
|
|
|
.timeline-text {
|
|
font-size: 0.875rem;
|
|
color: #495057;
|
|
}
|
|
|
|
@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-row {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.severity-selector {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.category-selector {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.location-selector {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.form-actions {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.btn-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:finding_list' %}">Findings</a></li>
|
|
<li class="breadcrumb-item active">{% if finding %}Edit{% else %}New{% endif %} Finding</li>
|
|
</ol>
|
|
<h1 class="page-header mb-0">
|
|
<i class="fas fa-search me-2"></i>{% if finding %}Edit Finding{% else %}Report New Finding{% endif %}
|
|
</h1>
|
|
</div>
|
|
<div class="ms-auto">
|
|
<a href="{% url 'quality:finding_list' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-arrow-left me-1"></i>Back to Findings
|
|
</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 finding %}{{ finding.title }}{% else %}Report Quality Finding{% endif %}</h2>
|
|
<p class="mb-0">{% if finding %}Update finding details and resolution progress{% else %}Document quality issues, safety concerns, and improvement opportunities{% endif %}</p>
|
|
</div>
|
|
<div class="col-md-4 text-md-end">
|
|
<div class="auto-save-indicator" id="auto-save-status">
|
|
<i class="fas fa-save"></i>
|
|
<span>Auto-save enabled</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" id="finding-form" enctype="multipart/form-data">
|
|
{% csrf_token %}
|
|
|
|
<!-- Validation Errors -->
|
|
{% if form.errors %}
|
|
<div class="validation-errors">
|
|
<h6><i class="fas fa-exclamation-triangle me-2"></i>Please correct the following errors:</h6>
|
|
<ul>
|
|
{% for field, errors in form.errors.items %}
|
|
{% for error in errors %}
|
|
<li>{{ field|title }}: {{ error }}</li>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="form-layout">
|
|
<!-- Main Form Content -->
|
|
<div class="form-main">
|
|
<!-- Basic Information -->
|
|
<div class="form-section">
|
|
<div class="section-header">
|
|
<i class="fas fa-info-circle"></i>
|
|
Basic Information
|
|
</div>
|
|
<div class="section-content">
|
|
<div class="form-row single">
|
|
<div class="form-group">
|
|
<label class="form-label required">Finding Title</label>
|
|
<input type="text" class="form-control" name="title"
|
|
value="{{ form.title.value|default:'' }}"
|
|
placeholder="Brief, descriptive title of the finding..." required>
|
|
<div class="form-text">A clear, concise title that summarizes the finding</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row single">
|
|
<div class="form-group">
|
|
<label class="form-label required">Description</label>
|
|
<textarea class="form-control" name="description" rows="4"
|
|
placeholder="Detailed description of the finding, including what was observed, when, and where..." required>{{ form.description.value|default:'' }}</textarea>
|
|
<div class="form-text">Provide comprehensive details about the finding</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Finding ID</label>
|
|
<input type="text" class="form-control" name="finding_id"
|
|
value="{{ form.finding_id.value|default:'' }}"
|
|
placeholder="Auto-generated if left blank">
|
|
<div class="form-text">Unique identifier for tracking</div>
|
|
</div>
|
|
|
|
<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>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Date Identified</label>
|
|
<input type="date" class="form-control" name="date_identified"
|
|
value="{{ form.date_identified.value|date:'Y-m-d'|default:'' }}">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Due Date</label>
|
|
<input type="date" class="form-control" name="due_date"
|
|
value="{{ form.due_date.value|date:'Y-m-d'|default:'' }}">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Classification -->
|
|
<div class="form-section">
|
|
<div class="section-header">
|
|
<i class="fas fa-tags"></i>
|
|
Classification
|
|
</div>
|
|
<div class="section-content">
|
|
<div class="form-group">
|
|
<label class="form-label required">Severity Level</label>
|
|
<div class="severity-selector">
|
|
<div class="severity-option severity-low" data-severity="low">
|
|
<i class="fas fa-arrow-down mb-2"></i>
|
|
<div><strong>Low</strong></div>
|
|
<div style="font-size: 0.75rem;">Minor impact</div>
|
|
</div>
|
|
<div class="severity-option severity-medium" data-severity="medium">
|
|
<i class="fas fa-minus mb-2"></i>
|
|
<div><strong>Medium</strong></div>
|
|
<div style="font-size: 0.75rem;">Moderate impact</div>
|
|
</div>
|
|
<div class="severity-option severity-high" data-severity="high">
|
|
<i class="fas fa-arrow-up mb-2"></i>
|
|
<div><strong>High</strong></div>
|
|
<div style="font-size: 0.75rem;">Significant impact</div>
|
|
</div>
|
|
<div class="severity-option severity-critical" data-severity="critical">
|
|
<i class="fas fa-exclamation-triangle mb-2"></i>
|
|
<div><strong>Critical</strong></div>
|
|
<div style="font-size: 0.75rem;">Immediate action</div>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" name="severity" id="severity-input" value="{{ form.severity.value|default:'medium' }}">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label required">Category</label>
|
|
<div class="category-selector">
|
|
<div class="category-option category-clinical" data-category="clinical">
|
|
<i class="fas fa-user-md mb-1"></i>
|
|
<div>Clinical</div>
|
|
</div>
|
|
<div class="category-option category-safety" data-category="safety">
|
|
<i class="fas fa-shield-alt mb-1"></i>
|
|
<div>Safety</div>
|
|
</div>
|
|
<div class="category-option category-compliance" data-category="compliance">
|
|
<i class="fas fa-gavel mb-1"></i>
|
|
<div>Compliance</div>
|
|
</div>
|
|
<div class="category-option category-process" data-category="process">
|
|
<i class="fas fa-cogs mb-1"></i>
|
|
<div>Process</div>
|
|
</div>
|
|
<div class="category-option category-documentation" data-category="documentation">
|
|
<i class="fas fa-file-alt mb-1"></i>
|
|
<div>Documentation</div>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" name="category" id="category-input" value="{{ form.category.value|default:'clinical' }}">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Location & Context -->
|
|
<div class="form-section">
|
|
<div class="section-header">
|
|
<i class="fas fa-map-marker-alt"></i>
|
|
Location & Context
|
|
</div>
|
|
<div class="section-content">
|
|
<div class="form-group">
|
|
<label class="form-label">Location</label>
|
|
<div class="location-selector">
|
|
<div class="location-option" data-location="Emergency Department">
|
|
<i class="fas fa-ambulance mb-1"></i>
|
|
<div>Emergency Dept</div>
|
|
</div>
|
|
<div class="location-option" data-location="Operating Theatre">
|
|
<i class="fas fa-procedures mb-1"></i>
|
|
<div>Operating Theatre</div>
|
|
</div>
|
|
<div class="location-option" data-location="ICU">
|
|
<i class="fas fa-heartbeat mb-1"></i>
|
|
<div>ICU</div>
|
|
</div>
|
|
<div class="location-option" data-location="General Ward">
|
|
<i class="fas fa-bed mb-1"></i>
|
|
<div>General Ward</div>
|
|
</div>
|
|
<div class="location-option" data-location="Laboratory">
|
|
<i class="fas fa-flask mb-1"></i>
|
|
<div>Laboratory</div>
|
|
</div>
|
|
<div class="location-option" data-location="Pharmacy">
|
|
<i class="fas fa-pills mb-1"></i>
|
|
<div>Pharmacy</div>
|
|
</div>
|
|
<div class="location-option" data-location="Radiology">
|
|
<i class="fas fa-x-ray mb-1"></i>
|
|
<div>Radiology</div>
|
|
</div>
|
|
<div class="location-option" data-location="Other">
|
|
<i class="fas fa-building mb-1"></i>
|
|
<div>Other</div>
|
|
</div>
|
|
</div>
|
|
<input type="text" class="form-control mt-2" name="location"
|
|
value="{{ form.location.value|default:'' }}"
|
|
placeholder="Specify location or select from above">
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label class="form-label">Circumstances</label>
|
|
<textarea class="form-control" name="circumstances" rows="3"
|
|
placeholder="Describe the circumstances surrounding the finding...">{{ form.circumstances.value|default:'' }}</textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Root Cause Analysis -->
|
|
<div class="form-section">
|
|
<div class="section-header">
|
|
<i class="fas fa-search-plus"></i>
|
|
Root Cause Analysis
|
|
</div>
|
|
<div class="section-content">
|
|
<div class="form-group">
|
|
<label class="form-label">Immediate Cause</label>
|
|
<textarea class="form-control" name="immediate_cause" rows="3"
|
|
placeholder="What directly caused this finding?">{{ form.immediate_cause.value|default:'' }}</textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Root Cause</label>
|
|
<textarea class="form-control" name="root_cause" rows="3"
|
|
placeholder="What is the underlying root cause?">{{ form.root_cause.value|default:'' }}</textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Contributing Factors</label>
|
|
<div class="risk-factors">
|
|
<div class="risk-factor">
|
|
<input type="checkbox" id="factor-communication" name="risk_factors" value="communication">
|
|
<label for="factor-communication">Communication Issues</label>
|
|
</div>
|
|
<div class="risk-factor">
|
|
<input type="checkbox" id="factor-training" name="risk_factors" value="training">
|
|
<label for="factor-training">Training Deficiency</label>
|
|
</div>
|
|
<div class="risk-factor">
|
|
<input type="checkbox" id="factor-equipment" name="risk_factors" value="equipment">
|
|
<label for="factor-equipment">Equipment Issues</label>
|
|
</div>
|
|
<div class="risk-factor">
|
|
<input type="checkbox" id="factor-process" name="risk_factors" value="process">
|
|
<label for="factor-process">Process Breakdown</label>
|
|
</div>
|
|
<div class="risk-factor">
|
|
<input type="checkbox" id="factor-workload" name="risk_factors" value="workload">
|
|
<label for="factor-workload">Workload Issues</label>
|
|
</div>
|
|
<div class="risk-factor">
|
|
<input type="checkbox" id="factor-environment" name="risk_factors" value="environment">
|
|
<label for="factor-environment">Environmental Factors</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Evidence & Documentation -->
|
|
<div class="form-section">
|
|
<div class="section-header">
|
|
<i class="fas fa-camera"></i>
|
|
Evidence & Documentation
|
|
</div>
|
|
<div class="section-content">
|
|
<div class="form-group">
|
|
<label class="form-label">Upload Evidence</label>
|
|
<div class="evidence-upload" id="evidence-upload">
|
|
<div class="upload-icon">
|
|
<i class="fas fa-cloud-upload-alt"></i>
|
|
</div>
|
|
<div class="upload-text">
|
|
<strong>Click to upload</strong> or drag and drop files here
|
|
</div>
|
|
<div class="upload-hint">
|
|
Photos, documents, reports (Max 10MB each)
|
|
</div>
|
|
<input type="file" id="evidence-files" multiple accept="image/*,.pdf,.doc,.docx" style="display: none;">
|
|
</div>
|
|
<div class="uploaded-files" id="uploaded-files"></div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Additional Notes</label>
|
|
<textarea class="form-control" name="additional_notes" rows="4"
|
|
placeholder="Any additional information, observations, or context...">{{ form.additional_notes.value|default:'' }}</textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="form-sidebar">
|
|
<!-- Assignment -->
|
|
<div class="form-section">
|
|
<div class="section-header">
|
|
<i class="fas fa-user-check"></i>
|
|
Assignment
|
|
</div>
|
|
<div class="section-content">
|
|
<div class="form-group">
|
|
<label class="form-label">Assign To</label>
|
|
<select class="form-select" name="assigned_to">
|
|
<option value="">Select User</option>
|
|
{% for user in assignable_users %}
|
|
<option value="{{ user.id }}" {% if form.assigned_to.value == user.id %}selected{% endif %}>
|
|
{{ user.get_full_name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Priority</label>
|
|
<select class="form-select" name="priority">
|
|
<option value="low" {% if form.priority.value == 'low' %}selected{% endif %}>Low Priority</option>
|
|
<option value="medium" {% if form.priority.value == 'medium' %}selected{% endif %}>Medium Priority</option>
|
|
<option value="high" {% if form.priority.value == 'high' %}selected{% endif %}>High Priority</option>
|
|
<option value="urgent" {% if form.priority.value == 'urgent' %}selected{% endif %}>Urgent</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Status</label>
|
|
<select class="form-select" name="status">
|
|
<option value="open" {% if form.status.value == 'open' %}selected{% endif %}>Open</option>
|
|
<option value="investigating" {% if form.status.value == 'investigating' %}selected{% endif %}>Investigating</option>
|
|
<option value="resolved" {% if form.status.value == 'resolved' %}selected{% endif %}>Resolved</option>
|
|
<option value="closed" {% if form.status.value == 'closed' %}selected{% endif %}>Closed</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Impact Assessment -->
|
|
<div class="form-section">
|
|
<div class="section-header">
|
|
<i class="fas fa-chart-line"></i>
|
|
Impact Assessment
|
|
</div>
|
|
<div class="section-content">
|
|
<div class="impact-assessment">
|
|
<div class="impact-item">
|
|
<div class="impact-icon">
|
|
<i class="fas fa-users"></i>
|
|
</div>
|
|
<div class="impact-label">Patient Impact</div>
|
|
<div class="impact-rating" data-rating="patient_impact">
|
|
<i class="fas fa-star rating-star" data-value="1"></i>
|
|
<i class="fas fa-star rating-star" data-value="2"></i>
|
|
<i class="fas fa-star rating-star" data-value="3"></i>
|
|
<i class="fas fa-star rating-star" data-value="4"></i>
|
|
<i class="fas fa-star rating-star" data-value="5"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="impact-item">
|
|
<div class="impact-icon">
|
|
<i class="fas fa-dollar-sign"></i>
|
|
</div>
|
|
<div class="impact-label">Financial Impact</div>
|
|
<div class="impact-rating" data-rating="financial_impact">
|
|
<i class="fas fa-star rating-star" data-value="1"></i>
|
|
<i class="fas fa-star rating-star" data-value="2"></i>
|
|
<i class="fas fa-star rating-star" data-value="3"></i>
|
|
<i class="fas fa-star rating-star" data-value="4"></i>
|
|
<i class="fas fa-star rating-star" data-value="5"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="impact-item">
|
|
<div class="impact-icon">
|
|
<i class="fas fa-building"></i>
|
|
</div>
|
|
<div class="impact-label">Operational Impact</div>
|
|
<div class="impact-rating" data-rating="operational_impact">
|
|
<i class="fas fa-star rating-star" data-value="1"></i>
|
|
<i class="fas fa-star rating-star" data-value="2"></i>
|
|
<i class="fas fa-star rating-star" data-value="3"></i>
|
|
<i class="fas fa-star rating-star" data-value="4"></i>
|
|
<i class="fas fa-star rating-star" data-value="5"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="impact-item">
|
|
<div class="impact-icon">
|
|
<i class="fas fa-balance-scale"></i>
|
|
</div>
|
|
<div class="impact-label">Regulatory Impact</div>
|
|
<div class="impact-rating" data-rating="regulatory_impact">
|
|
<i class="fas fa-star rating-star" data-value="1"></i>
|
|
<i class="fas fa-star rating-star" data-value="2"></i>
|
|
<i class="fas fa-star rating-star" data-value="3"></i>
|
|
<i class="fas fa-star rating-star" data-value="4"></i>
|
|
<i class="fas fa-star rating-star" data-value="5"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<input type="hidden" name="patient_impact" id="patient_impact" value="{{ form.patient_impact.value|default:1 }}">
|
|
<input type="hidden" name="financial_impact" id="financial_impact" value="{{ form.financial_impact.value|default:1 }}">
|
|
<input type="hidden" name="operational_impact" id="operational_impact" value="{{ form.operational_impact.value|default:1 }}">
|
|
<input type="hidden" name="regulatory_impact" id="regulatory_impact" value="{{ form.regulatory_impact.value|default:1 }}">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resolution Timeline -->
|
|
<div class="form-section">
|
|
<div class="section-header">
|
|
<i class="fas fa-timeline"></i>
|
|
Resolution Timeline
|
|
</div>
|
|
<div class="section-content">
|
|
<div class="timeline-preview">
|
|
<div class="timeline-item">
|
|
<div class="timeline-dot active"></div>
|
|
<div class="timeline-text">Finding Reported</div>
|
|
</div>
|
|
<div class="timeline-item">
|
|
<div class="timeline-dot"></div>
|
|
<div class="timeline-text">Investigation Started</div>
|
|
</div>
|
|
<div class="timeline-item">
|
|
<div class="timeline-dot"></div>
|
|
<div class="timeline-text">Root Cause Identified</div>
|
|
</div>
|
|
<div class="timeline-item">
|
|
<div class="timeline-dot"></div>
|
|
<div class="timeline-text">Corrective Actions</div>
|
|
</div>
|
|
<div class="timeline-item">
|
|
<div class="timeline-dot"></div>
|
|
<div class="timeline-text">Resolution Verified</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if finding %}
|
|
<div class="mt-3">
|
|
<label class="form-label">Resolution Progress</label>
|
|
<div class="progress-container">
|
|
<div class="progress-header">
|
|
<span class="progress-percentage" id="progress-value">{{ form.resolution_progress.value|default:0 }}%</span>
|
|
</div>
|
|
<input type="range" class="form-range" name="resolution_progress"
|
|
value="{{ form.resolution_progress.value|default:0 }}"
|
|
min="0" max="100" step="5" id="progress-slider">
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tags & Keywords -->
|
|
<div class="form-section">
|
|
<div class="section-header">
|
|
<i class="fas fa-tags"></i>
|
|
Tags & Keywords
|
|
</div>
|
|
<div class="section-content">
|
|
<div class="form-group">
|
|
<label class="form-label">Tags</label>
|
|
<input type="text" class="form-control" name="tags"
|
|
value="{{ form.tags.value|default:'' }}"
|
|
placeholder="Enter tags separated by commas">
|
|
<div class="form-text">Use tags to categorize and search findings</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Keywords</label>
|
|
<input type="text" class="form-control" name="keywords"
|
|
value="{{ form.keywords.value|default:'' }}"
|
|
placeholder="Key terms for searching">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="form-actions">
|
|
<div class="auto-save-indicator" id="auto-save-indicator">
|
|
<i class="fas fa-save"></i>
|
|
<span>All changes saved</span>
|
|
</div>
|
|
|
|
<div class="btn-group">
|
|
<a href="{% url 'quality:finding_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-danger">
|
|
<i class="fas fa-check me-1"></i>{% if finding %}Update Finding{% else %}Report Finding{% endif %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script src="{% static 'assets/plugins/select2/dist/js/select2.min.js' %}"></script>
|
|
<script src="{% static 'assets/plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>
|
|
|
|
<script>
|
|
let autoSaveTimer;
|
|
let uploadedFiles = [];
|
|
|
|
$(document).ready(function() {
|
|
// Initialize form
|
|
initializeForm();
|
|
|
|
// Set up auto-save
|
|
setupAutoSave();
|
|
|
|
// Initialize selectors
|
|
initializeSeveritySelector();
|
|
initializeCategorySelector();
|
|
initializeLocationSelector();
|
|
initializeImpactRatings();
|
|
|
|
// Initialize file upload
|
|
initializeFileUpload();
|
|
|
|
// Set initial values
|
|
setInitialValues();
|
|
});
|
|
|
|
function initializeForm() {
|
|
// Initialize Select2 for dropdowns
|
|
$('.form-select').select2({
|
|
width: '100%',
|
|
placeholder: function() {
|
|
return $(this).data('placeholder') || 'Select an option...';
|
|
}
|
|
});
|
|
|
|
// Initialize date pickers
|
|
$('input[type="date"]').datepicker({
|
|
format: 'yyyy-mm-dd',
|
|
autoclose: true,
|
|
todayHighlight: true
|
|
});
|
|
|
|
// Initialize progress slider
|
|
$('#progress-slider').on('input', function() {
|
|
const value = $(this).val();
|
|
$('#progress-value').text(value + '%');
|
|
});
|
|
}
|
|
|
|
function setupAutoSave() {
|
|
// Auto-save form data every 30 seconds
|
|
$('#finding-form input, #finding-form textarea, #finding-form select').on('input change', function() {
|
|
clearTimeout(autoSaveTimer);
|
|
|
|
$('#auto-save-indicator').removeClass('saved').addClass('saving');
|
|
$('#auto-save-indicator span').text('Saving...');
|
|
|
|
autoSaveTimer = setTimeout(function() {
|
|
saveDraft();
|
|
}, 2000);
|
|
});
|
|
}
|
|
|
|
function initializeSeveritySelector() {
|
|
$('.severity-option').click(function() {
|
|
$('.severity-option').removeClass('selected');
|
|
$(this).addClass('selected');
|
|
$('#severity-input').val($(this).data('severity'));
|
|
});
|
|
}
|
|
|
|
function initializeCategorySelector() {
|
|
$('.category-option').click(function() {
|
|
$('.category-option').removeClass('selected');
|
|
$(this).addClass('selected');
|
|
$('#category-input').val($(this).data('category'));
|
|
});
|
|
}
|
|
|
|
function initializeLocationSelector() {
|
|
$('.location-option').click(function() {
|
|
$('.location-option').removeClass('selected');
|
|
$(this).addClass('selected');
|
|
$('input[name="location"]').val($(this).data('location'));
|
|
});
|
|
}
|
|
|
|
function initializeImpactRatings() {
|
|
$('.impact-rating').each(function() {
|
|
const ratingType = $(this).data('rating');
|
|
const currentValue = $(`#${ratingType}`).val() || 1;
|
|
|
|
// Set initial rating
|
|
$(this).find('.rating-star').each(function(index) {
|
|
if (index < currentValue) {
|
|
$(this).addClass('active');
|
|
}
|
|
});
|
|
|
|
// Handle rating clicks
|
|
$(this).find('.rating-star').click(function() {
|
|
const value = $(this).data('value');
|
|
const container = $(this).closest('.impact-rating');
|
|
const ratingType = container.data('rating');
|
|
|
|
// Update visual rating
|
|
container.find('.rating-star').removeClass('active');
|
|
container.find('.rating-star').each(function(index) {
|
|
if (index < value) {
|
|
$(this).addClass('active');
|
|
}
|
|
});
|
|
|
|
// Update hidden input
|
|
$(`#${ratingType}`).val(value);
|
|
});
|
|
});
|
|
}
|
|
|
|
function initializeFileUpload() {
|
|
const uploadArea = document.getElementById('evidence-upload');
|
|
const fileInput = document.getElementById('evidence-files');
|
|
|
|
// Click to upload
|
|
uploadArea.addEventListener('click', () => {
|
|
fileInput.click();
|
|
});
|
|
|
|
// Drag and drop
|
|
uploadArea.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
uploadArea.classList.add('dragover');
|
|
});
|
|
|
|
uploadArea.addEventListener('dragleave', () => {
|
|
uploadArea.classList.remove('dragover');
|
|
});
|
|
|
|
uploadArea.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
uploadArea.classList.remove('dragover');
|
|
|
|
const files = Array.from(e.dataTransfer.files);
|
|
handleFileUpload(files);
|
|
});
|
|
|
|
// File input change
|
|
fileInput.addEventListener('change', (e) => {
|
|
const files = Array.from(e.target.files);
|
|
handleFileUpload(files);
|
|
});
|
|
}
|
|
|
|
function handleFileUpload(files) {
|
|
files.forEach(file => {
|
|
if (file.size > 10 * 1024 * 1024) { // 10MB limit
|
|
showAlert(`File ${file.name} is too large (max 10MB)`, 'warning');
|
|
return;
|
|
}
|
|
|
|
const fileId = Date.now() + Math.random();
|
|
uploadedFiles.push({
|
|
id: fileId,
|
|
file: file,
|
|
name: file.name,
|
|
size: file.size,
|
|
type: file.type
|
|
});
|
|
|
|
addFileToDisplay(fileId, file);
|
|
});
|
|
}
|
|
|
|
function addFileToDisplay(fileId, file) {
|
|
const fileItem = document.createElement('div');
|
|
fileItem.className = 'file-item';
|
|
fileItem.innerHTML = `
|
|
<div class="file-icon">
|
|
<i class="fas fa-file-${getFileIcon(file.type)}"></i>
|
|
</div>
|
|
<div class="file-info">
|
|
<div class="file-name">${file.name}</div>
|
|
<div class="file-meta">${formatFileSize(file.size)} • ${file.type}</div>
|
|
</div>
|
|
<button type="button" class="file-remove" onclick="removeFile(${fileId})">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
`;
|
|
|
|
document.getElementById('uploaded-files').appendChild(fileItem);
|
|
}
|
|
|
|
function removeFile(fileId) {
|
|
uploadedFiles = uploadedFiles.filter(f => f.id !== fileId);
|
|
|
|
// Remove from display
|
|
const fileItems = document.querySelectorAll('.file-item');
|
|
fileItems.forEach(item => {
|
|
const removeBtn = item.querySelector('.file-remove');
|
|
if (removeBtn && removeBtn.onclick.toString().includes(fileId)) {
|
|
item.remove();
|
|
}
|
|
});
|
|
}
|
|
|
|
function getFileIcon(mimeType) {
|
|
if (mimeType.startsWith('image/')) return 'image';
|
|
if (mimeType.includes('pdf')) return 'pdf';
|
|
if (mimeType.includes('word') || mimeType.includes('document')) return 'word';
|
|
if (mimeType.includes('excel') || mimeType.includes('spreadsheet')) return 'excel';
|
|
return 'alt';
|
|
}
|
|
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
function setInitialValues() {
|
|
// Set initial severity
|
|
const initialSeverity = $('#severity-input').val();
|
|
$(`.severity-option[data-severity="${initialSeverity}"]`).addClass('selected');
|
|
|
|
// Set initial category
|
|
const initialCategory = $('#category-input').val();
|
|
$(`.category-option[data-category="${initialCategory}"]`).addClass('selected');
|
|
|
|
// Set initial location if exists
|
|
const initialLocation = $('input[name="location"]').val();
|
|
if (initialLocation) {
|
|
$(`.location-option[data-location="${initialLocation}"]`).addClass('selected');
|
|
}
|
|
}
|
|
|
|
function saveDraft() {
|
|
const formData = new FormData($('#finding-form')[0]);
|
|
formData.append('save_draft', 'true');
|
|
|
|
// Add uploaded files
|
|
uploadedFiles.forEach(fileObj => {
|
|
formData.append('evidence_files', fileObj.file);
|
|
});
|
|
|
|
fetch(window.location.href, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
|
},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
$('#auto-save-indicator').removeClass('saving').addClass('saved');
|
|
$('#auto-save-indicator span').text('Draft saved');
|
|
|
|
setTimeout(() => {
|
|
$('#auto-save-indicator').removeClass('saved');
|
|
$('#auto-save-indicator span').text('Auto-save enabled');
|
|
}, 3000);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Auto-save error:', error);
|
|
$('#auto-save-indicator').removeClass('saving');
|
|
$('#auto-save-indicator span').text('Save failed');
|
|
});
|
|
}
|
|
|
|
// Form validation
|
|
$('#finding-form').on('submit', function(e) {
|
|
let isValid = true;
|
|
const errors = [];
|
|
|
|
// Validate required fields
|
|
if (!$('input[name="title"]').val().trim()) {
|
|
errors.push('Finding title is required');
|
|
isValid = false;
|
|
}
|
|
|
|
if (!$('textarea[name="description"]').val().trim()) {
|
|
errors.push('Finding description is required');
|
|
isValid = false;
|
|
}
|
|
|
|
if (!$('select[name="department"]').val()) {
|
|
errors.push('Department selection is required');
|
|
isValid = false;
|
|
}
|
|
|
|
if (!$('#severity-input').val()) {
|
|
errors.push('Severity level is required');
|
|
isValid = false;
|
|
}
|
|
|
|
if (!$('#category-input').val()) {
|
|
errors.push('Category selection is required');
|
|
isValid = false;
|
|
}
|
|
|
|
// Validate date range
|
|
const dateIdentified = new Date($('input[name="date_identified"]').val());
|
|
const dueDate = new Date($('input[name="due_date"]').val());
|
|
|
|
if (dateIdentified && dueDate && dateIdentified >= dueDate) {
|
|
errors.push('Due date must be after the date identified');
|
|
isValid = false;
|
|
}
|
|
|
|
if (!isValid) {
|
|
e.preventDefault();
|
|
showAlert('Please correct the following errors:\n' + errors.join('\n'), 'danger');
|
|
} else {
|
|
// Add uploaded files to form
|
|
const formData = new FormData(this);
|
|
uploadedFiles.forEach(fileObj => {
|
|
formData.append('evidence_files', fileObj.file);
|
|
});
|
|
}
|
|
});
|
|
|
|
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 %}
|
|
|