661 lines
33 KiB
HTML
661 lines
33 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% if widget %}Edit Widget{% else %}Create Widget{% endif %} - Hospital Management{% 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>{% if widget %}Edit Widget{% else %}Create New Widget{% endif %}</h4>
|
|
<h6>{% if widget %}Update widget configuration{% else %}Add a new widget to your dashboard{% endif %}</h6>
|
|
</div>
|
|
<div class="page-btn">
|
|
<a href="{% url 'analytics:dashboard_widget_list' dashboard.pk|default:1 %}" class="btn btn-secondary">
|
|
<i class="fas fa-arrow-left me-1"></i>Back to Widgets
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" id="widgetForm">
|
|
{% csrf_token %}
|
|
<div class="row">
|
|
<!-- Widget Configuration -->
|
|
<div class="col-lg-8">
|
|
<!-- Basic Information -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-info-circle me-2"></i>Basic Information
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="title" class="form-label">Widget Title <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="title" name="title"
|
|
value="{{ widget.title|default:'' }}"
|
|
placeholder="Enter widget title" required>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="widget_type" class="form-label">Widget Type <span class="text-danger">*</span></label>
|
|
<select class="form-select" id="widget_type" name="widget_type" required onchange="updateWidgetOptions()">
|
|
<option value="">Select widget type</option>
|
|
<option value="CHART" {% if widget.widget_type == 'CHART' %}selected{% endif %}>Chart</option>
|
|
<option value="TABLE" {% if widget.widget_type == 'TABLE' %}selected{% endif %}>Table</option>
|
|
<option value="METRIC" {% if widget.widget_type == 'METRIC' %}selected{% endif %}>Metric/KPI</option>
|
|
<option value="GAUGE" {% if widget.widget_type == 'GAUGE' %}selected{% endif %}>Gauge</option>
|
|
<option value="MAP" {% if widget.widget_type == 'MAP' %}selected{% endif %}>Map</option>
|
|
<option value="TEXT" {% if widget.widget_type == 'TEXT' %}selected{% endif %}>Text</option>
|
|
<option value="CUSTOM" {% if widget.widget_type == 'CUSTOM' %}selected{% endif %}>Custom</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="description" class="form-label">Description</label>
|
|
<textarea class="form-control" id="description" name="description" rows="3"
|
|
placeholder="Describe what this widget displays">{{ widget.description|default:'' }}</textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chart Configuration -->
|
|
<div class="card" id="chartConfig" style="display: none;">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-chart-line me-2"></i>Chart Configuration
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="chart_type" class="form-label">Chart Type</label>
|
|
<select class="form-select" id="chart_type" name="chart_type">
|
|
<option value="LINE" {% if widget.chart_type == 'LINE' %}selected{% endif %}>Line Chart</option>
|
|
<option value="BAR" {% if widget.chart_type == 'BAR' %}selected{% endif %}>Bar Chart</option>
|
|
<option value="PIE" {% if widget.chart_type == 'PIE' %}selected{% endif %}>Pie Chart</option>
|
|
<option value="AREA" {% if widget.chart_type == 'AREA' %}selected{% endif %}>Area Chart</option>
|
|
<option value="SCATTER" {% if widget.chart_type == 'SCATTER' %}selected{% endif %}>Scatter Plot</option>
|
|
<option value="DOUGHNUT" {% if widget.chart_type == 'DOUGHNUT' %}selected{% endif %}>Doughnut Chart</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="color_scheme" class="form-label">Color Scheme</label>
|
|
<select class="form-select" id="color_scheme" name="color_scheme">
|
|
<option value="default">Default</option>
|
|
<option value="blue">Blue Tones</option>
|
|
<option value="green">Green Tones</option>
|
|
<option value="red">Red Tones</option>
|
|
<option value="purple">Purple Tones</option>
|
|
<option value="rainbow">Rainbow</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="show_legend" name="show_legend"
|
|
{% if widget.show_legend or not widget %}checked{% endif %}>
|
|
<label class="form-check-label" for="show_legend">Show Legend</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="show_grid" name="show_grid"
|
|
{% if widget.show_grid %}checked{% endif %}>
|
|
<label class="form-check-label" for="show_grid">Show Grid</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="enable_zoom" name="enable_zoom"
|
|
{% if widget.enable_zoom %}checked{% endif %}>
|
|
<label class="form-check-label" for="enable_zoom">Enable Zoom</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Data Configuration -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-database me-2"></i>Data Configuration
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="data_source" class="form-label">Data Source <span class="text-danger">*</span></label>
|
|
<select class="form-select" id="data_source" name="data_source" required onchange="updateMetrics()">
|
|
<option value="">Select data source</option>
|
|
<option value="patient_data" {% if widget.data_source == 'patient_data' %}selected{% endif %}>Patient Data</option>
|
|
<option value="financial_data" {% if widget.data_source == 'financial_data' %}selected{% endif %}>Financial Data</option>
|
|
<option value="clinical_data" {% if widget.data_source == 'clinical_data' %}selected{% endif %}>Clinical Data</option>
|
|
<option value="operational_data" {% if widget.data_source == 'operational_data' %}selected{% endif %}>Operational Data</option>
|
|
<option value="quality_data" {% if widget.data_source == 'quality_data' %}selected{% endif %}>Quality Data</option>
|
|
<option value="hr_data" {% if widget.data_source == 'hr_data' %}selected{% endif %}>HR Data</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="metric" class="form-label">Primary Metric <span class="text-danger">*</span></label>
|
|
<select class="form-select" id="metric" name="metric" required>
|
|
<option value="">Select metric</option>
|
|
<!-- Options will be populated based on data source -->
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="time_period" class="form-label">Time Period</label>
|
|
<select class="form-select" id="time_period" name="time_period">
|
|
<option value="7d" {% if widget.time_period == '7d' %}selected{% endif %}>Last 7 days</option>
|
|
<option value="30d" {% if widget.time_period == '30d' or not widget %}selected{% endif %}>Last 30 days</option>
|
|
<option value="90d" {% if widget.time_period == '90d' %}selected{% endif %}>Last 90 days</option>
|
|
<option value="6m" {% if widget.time_period == '6m' %}selected{% endif %}>Last 6 months</option>
|
|
<option value="1y" {% if widget.time_period == '1y' %}selected{% endif %}>Last year</option>
|
|
<option value="custom" {% if widget.time_period == 'custom' %}selected{% endif %}>Custom Range</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="refresh_interval" class="form-label">Refresh Interval</label>
|
|
<select class="form-select" id="refresh_interval" name="refresh_interval">
|
|
<option value="60" {% if widget.refresh_interval == 60 %}selected{% endif %}>1 minute</option>
|
|
<option value="300" {% if widget.refresh_interval == 300 or not widget %}selected{% endif %}>5 minutes</option>
|
|
<option value="600" {% if widget.refresh_interval == 600 %}selected{% endif %}>10 minutes</option>
|
|
<option value="900" {% if widget.refresh_interval == 900 %}selected{% endif %}>15 minutes</option>
|
|
<option value="1800" {% if widget.refresh_interval == 1800 %}selected{% endif %}>30 minutes</option>
|
|
<option value="3600" {% if widget.refresh_interval == 3600 %}selected{% endif %}>1 hour</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" id="customDateRange" style="display: none;">
|
|
<label class="form-label">Custom Date Range</label>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<input type="date" class="form-control" id="start_date" name="start_date"
|
|
value="{{ widget.start_date|default:'' }}">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<input type="date" class="form-control" id="end_date" name="end_date"
|
|
value="{{ widget.end_date|default:'' }}">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Layout Configuration -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-th-large me-2"></i>Layout Configuration
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label for="grid_x" class="form-label">Column Start</label>
|
|
<select class="form-select" id="grid_x" name="grid_x">
|
|
{% for i in "123456789012"|make_list %}
|
|
<option value="{{ forloop.counter }}" {% if widget.grid_x == forloop.counter %}selected{% endif %}>{{ forloop.counter }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label for="grid_y" class="form-label">Row Start</label>
|
|
<select class="form-select" id="grid_y" name="grid_y">
|
|
{% for i in "123456"|make_list %}
|
|
<option value="{{ forloop.counter }}" {% if widget.grid_y == forloop.counter %}selected{% endif %}>{{ forloop.counter }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label for="grid_width" class="form-label">Width (columns)</label>
|
|
<select class="form-select" id="grid_width" name="grid_width">
|
|
{% for i in "123456789012"|make_list %}
|
|
<option value="{{ forloop.counter }}" {% if widget.grid_width == forloop.counter or forloop.counter == 6 and not widget %}selected{% endif %}>{{ forloop.counter }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label for="grid_height" class="form-label">Height (rows)</label>
|
|
<select class="form-select" id="grid_height" name="grid_height">
|
|
{% for i in "123456"|make_list %}
|
|
<option value="{{ forloop.counter }}" {% if widget.grid_height == forloop.counter or forloop.counter == 3 and not widget %}selected{% endif %}>{{ forloop.counter }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid-preview-container">
|
|
<label class="form-label">Position Preview</label>
|
|
<div class="grid-preview" id="gridPreview">
|
|
<!-- Grid will be generated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-lg-4">
|
|
<!-- Widget Preview -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-eye me-2"></i>Widget Preview
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="widget-preview" id="widgetPreview">
|
|
<div class="preview-placeholder">
|
|
<i class="fas fa-chart-line fa-3x text-muted"></i>
|
|
<p class="text-muted mt-2">Select widget type to see preview</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Widget Settings -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-cog me-2"></i>Widget Settings
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="form-group">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="is_active" name="is_active"
|
|
{% if widget.is_active or not widget %}checked{% endif %}>
|
|
<label class="form-check-label" for="is_active">Active Widget</label>
|
|
</div>
|
|
<div class="form-text">Inactive widgets are hidden from the dashboard</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="allow_export" name="allow_export"
|
|
{% if widget.allow_export or not widget %}checked{% endif %}>
|
|
<label class="form-check-label" for="allow_export">Allow Export</label>
|
|
</div>
|
|
<div class="form-text">Users can export widget data</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="allow_drilldown" name="allow_drilldown"
|
|
{% if widget.allow_drilldown %}checked{% endif %}>
|
|
<label class="form-check-label" for="allow_drilldown">Allow Drill-down</label>
|
|
</div>
|
|
<div class="form-text">Users can click for detailed views</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="real_time" name="real_time"
|
|
{% if widget.real_time %}checked{% endif %}>
|
|
<label class="form-check-label" for="real_time">Real-time Updates</label>
|
|
</div>
|
|
<div class="form-text">Widget updates in real-time</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save me-1"></i>
|
|
{% if widget %}Update Widget{% else %}Create Widget{% endif %}
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="previewWidget()">
|
|
<i class="fas fa-eye me-1"></i>Preview Widget
|
|
</button>
|
|
<a href="{% url 'analytics:dashboard_widget_list' dashboard.pk|default:1 %}" class="btn btn-outline-danger">
|
|
<i class="fas fa-times me-1"></i>Cancel
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize form
|
|
updateWidgetOptions();
|
|
updateGridPreview();
|
|
|
|
// Add event listeners
|
|
document.getElementById('time_period').addEventListener('change', handleTimePeriodChange);
|
|
|
|
// Grid position listeners
|
|
['grid_x', 'grid_y', 'grid_width', 'grid_height'].forEach(id => {
|
|
document.getElementById(id).addEventListener('change', updateGridPreview);
|
|
});
|
|
|
|
// Form change listeners for preview
|
|
const formInputs = document.querySelectorAll('#widgetForm input, #widgetForm select, #widgetForm textarea');
|
|
formInputs.forEach(input => {
|
|
input.addEventListener('change', updatePreview);
|
|
});
|
|
});
|
|
|
|
function updateWidgetOptions() {
|
|
const widgetType = document.getElementById('widget_type').value;
|
|
const chartConfig = document.getElementById('chartConfig');
|
|
|
|
if (widgetType === 'CHART') {
|
|
chartConfig.style.display = 'block';
|
|
} else {
|
|
chartConfig.style.display = 'none';
|
|
}
|
|
|
|
updatePreview();
|
|
}
|
|
|
|
function updateMetrics() {
|
|
const dataSource = document.getElementById('data_source').value;
|
|
const metricSelect = document.getElementById('metric');
|
|
|
|
// Clear existing options
|
|
metricSelect.innerHTML = '<option value="">Select metric</option>';
|
|
|
|
const metrics = {
|
|
'patient_data': [
|
|
{ value: 'patient_count', text: 'Patient Count' },
|
|
{ value: 'new_patients', text: 'New Patients' },
|
|
{ value: 'patient_satisfaction', text: 'Patient Satisfaction' },
|
|
{ value: 'readmission_rate', text: 'Readmission Rate' }
|
|
],
|
|
'financial_data': [
|
|
{ value: 'revenue', text: 'Revenue' },
|
|
{ value: 'expenses', text: 'Expenses' },
|
|
{ value: 'profit_margin', text: 'Profit Margin' },
|
|
{ value: 'billing_amount', text: 'Billing Amount' }
|
|
],
|
|
'clinical_data': [
|
|
{ value: 'lab_results', text: 'Lab Results' },
|
|
{ value: 'procedures', text: 'Procedures' },
|
|
{ value: 'medications', text: 'Medications' },
|
|
{ value: 'diagnoses', text: 'Diagnoses' }
|
|
],
|
|
'operational_data': [
|
|
{ value: 'bed_occupancy', text: 'Bed Occupancy' },
|
|
{ value: 'staff_utilization', text: 'Staff Utilization' },
|
|
{ value: 'equipment_usage', text: 'Equipment Usage' },
|
|
{ value: 'wait_times', text: 'Wait Times' }
|
|
],
|
|
'quality_data': [
|
|
{ value: 'quality_score', text: 'Quality Score' },
|
|
{ value: 'incident_rate', text: 'Incident Rate' },
|
|
{ value: 'compliance_rate', text: 'Compliance Rate' },
|
|
{ value: 'safety_metrics', text: 'Safety Metrics' }
|
|
],
|
|
'hr_data': [
|
|
{ value: 'staff_count', text: 'Staff Count' },
|
|
{ value: 'turnover_rate', text: 'Turnover Rate' },
|
|
{ value: 'training_completion', text: 'Training Completion' },
|
|
{ value: 'performance_scores', text: 'Performance Scores' }
|
|
]
|
|
};
|
|
|
|
if (metrics[dataSource]) {
|
|
metrics[dataSource].forEach(metric => {
|
|
const option = document.createElement('option');
|
|
option.value = metric.value;
|
|
option.textContent = metric.text;
|
|
metricSelect.appendChild(option);
|
|
});
|
|
}
|
|
}
|
|
|
|
function handleTimePeriodChange() {
|
|
const timePeriod = document.getElementById('time_period').value;
|
|
const customDateRange = document.getElementById('customDateRange');
|
|
|
|
if (timePeriod === 'custom') {
|
|
customDateRange.style.display = 'block';
|
|
} else {
|
|
customDateRange.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function updateGridPreview() {
|
|
const gridX = parseInt(document.getElementById('grid_x').value) || 1;
|
|
const gridY = parseInt(document.getElementById('grid_y').value) || 1;
|
|
const gridWidth = parseInt(document.getElementById('grid_width').value) || 6;
|
|
const gridHeight = parseInt(document.getElementById('grid_height').value) || 3;
|
|
|
|
const preview = document.getElementById('gridPreview');
|
|
preview.innerHTML = '';
|
|
|
|
// Create 12x6 grid
|
|
for (let row = 1; row <= 6; row++) {
|
|
for (let col = 1; col <= 12; col++) {
|
|
const cell = document.createElement('div');
|
|
cell.className = 'grid-cell';
|
|
|
|
// Check if this cell is part of the widget
|
|
if (row >= gridY && row < gridY + gridHeight &&
|
|
col >= gridX && col < gridX + gridWidth) {
|
|
cell.classList.add('selected');
|
|
}
|
|
|
|
preview.appendChild(cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
function updatePreview() {
|
|
const widgetType = document.getElementById('widget_type').value;
|
|
const title = document.getElementById('title').value || 'Widget Title';
|
|
const preview = document.getElementById('widgetPreview');
|
|
|
|
if (!widgetType) {
|
|
preview.innerHTML = `
|
|
<div class="preview-placeholder">
|
|
<i class="fas fa-chart-line fa-3x text-muted"></i>
|
|
<p class="text-muted mt-2">Select widget type to see preview</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
const icons = {
|
|
'CHART': 'fas fa-chart-line',
|
|
'TABLE': 'fas fa-table',
|
|
'METRIC': 'fas fa-calculator',
|
|
'GAUGE': 'fas fa-tachometer-alt',
|
|
'MAP': 'fas fa-map',
|
|
'TEXT': 'fas fa-font',
|
|
'CUSTOM': 'fas fa-cog'
|
|
};
|
|
|
|
const colors = {
|
|
'CHART': 'text-primary',
|
|
'TABLE': 'text-info',
|
|
'METRIC': 'text-success',
|
|
'GAUGE': 'text-warning',
|
|
'MAP': 'text-danger',
|
|
'TEXT': 'text-secondary',
|
|
'CUSTOM': 'text-dark'
|
|
};
|
|
|
|
preview.innerHTML = `
|
|
<div class="preview-widget">
|
|
<div class="preview-header">
|
|
<h6>${title}</h6>
|
|
</div>
|
|
<div class="preview-content">
|
|
<i class="${icons[widgetType]} fa-3x ${colors[widgetType]}"></i>
|
|
<p class="mt-2 text-muted">${widgetType} Widget</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function previewWidget() {
|
|
const formData = new FormData(document.getElementById('widgetForm'));
|
|
const widgetData = Object.fromEntries(formData.entries());
|
|
|
|
alert('Opening widget preview...\n\nWidget: ' + widgetData.title + '\nType: ' + widgetData.widget_type);
|
|
}
|
|
|
|
// Form validation
|
|
document.getElementById('widgetForm').addEventListener('submit', function(e) {
|
|
const title = document.getElementById('title').value.trim();
|
|
const widgetType = document.getElementById('widget_type').value;
|
|
const dataSource = document.getElementById('data_source').value;
|
|
const metric = document.getElementById('metric').value;
|
|
|
|
if (!title || !widgetType || !dataSource || !metric) {
|
|
e.preventDefault();
|
|
alert('Please fill in all required fields');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const submitBtn = document.querySelector('button[type="submit"]');
|
|
const originalText = submitBtn.innerHTML;
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Saving...';
|
|
submitBtn.disabled = true;
|
|
|
|
// Re-enable after delay (in real app, handled by form submission)
|
|
setTimeout(() => {
|
|
submitBtn.innerHTML = originalText;
|
|
submitBtn.disabled = false;
|
|
}, 2000);
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.grid-preview-container {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.grid-preview {
|
|
display: grid;
|
|
grid-template-columns: repeat(12, 1fr);
|
|
grid-template-rows: repeat(6, 25px);
|
|
gap: 2px;
|
|
background: #f8f9fa;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
border: 1px solid #e9ecef;
|
|
}
|
|
|
|
.grid-cell {
|
|
background: #e9ecef;
|
|
border-radius: 3px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.grid-cell.selected {
|
|
background: #007bff;
|
|
}
|
|
|
|
.widget-preview {
|
|
min-height: 200px;
|
|
border: 1px solid #e9ecef;
|
|
border-radius: 8px;
|
|
background: #f8f9fa;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.preview-placeholder {
|
|
text-align: center;
|
|
padding: 40px 20px;
|
|
}
|
|
|
|
.preview-widget {
|
|
text-align: center;
|
|
padding: 20px;
|
|
background: white;
|
|
border-radius: 8px;
|
|
width: 100%;
|
|
margin: 10px;
|
|
}
|
|
|
|
.preview-header {
|
|
border-bottom: 1px solid #e9ecef;
|
|
padding-bottom: 10px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.preview-header h6 {
|
|
margin: 0;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
}
|
|
|
|
.preview-content {
|
|
padding: 20px 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.grid-preview {
|
|
grid-template-columns: repeat(6, 1fr);
|
|
grid-template-rows: repeat(8, 20px);
|
|
}
|
|
|
|
.preview-widget {
|
|
margin: 5px;
|
|
padding: 15px;
|
|
}
|
|
|
|
.preview-content {
|
|
padding: 15px 0;
|
|
}
|
|
|
|
.preview-content i {
|
|
font-size: 2rem !important;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|