557 lines
28 KiB
HTML
557 lines
28 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% if object %}Edit {{ object.display_name }}{% else %}Add New Tenant{% endif %} - {{ block.super }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-1">
|
|
{% if object %}
|
|
Edit Tenant: {{ object.display_name }}
|
|
{% else %}
|
|
Add New Tenant
|
|
{% endif %}
|
|
</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'core:tenant_list' %}">Tenants</a></li>
|
|
{% if object %}
|
|
<li class="breadcrumb-item"><a href="{% url 'core:tenant_detail' object.pk %}">{{ object.name }}</a></li>
|
|
<li class="breadcrumb-item active">Edit</li>
|
|
{% else %}
|
|
<li class="breadcrumb-item active">Add New</li>
|
|
{% endif %}
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div class="btn-group">
|
|
<a href="{% if object %}{% url 'core:tenant_detail' object.pk %}{% else %}{% url 'core:tenant_list' %}{% endif %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-arrow-left me-2"></i>Back
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" novalidate>
|
|
{% csrf_token %}
|
|
|
|
<div class="row">
|
|
<!-- Main Form -->
|
|
<div class="col-lg-8">
|
|
<!-- Basic Information -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<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="mb-3">
|
|
<label for="{{ form.name.id_for_label }}" class="form-label">
|
|
Organization Name <span class="text-danger">*</span>
|
|
</label>
|
|
{{ form.name }}
|
|
{% if form.name.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.name.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Internal name for the organization (used in system)</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.display_name.id_for_label }}" class="form-label">
|
|
Display Name <span class="text-danger">*</span>
|
|
</label>
|
|
{{ form.display_name }}
|
|
{% if form.display_name.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.display_name.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Public-facing name shown to users</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.organization_type.id_for_label }}" class="form-label">
|
|
Organization Type <span class="text-danger">*</span>
|
|
</label>
|
|
{{ form.organization_type }}
|
|
{% if form.organization_type.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.organization_type.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.domain.id_for_label }}" class="form-label">
|
|
Domain
|
|
</label>
|
|
{{ form.domain }}
|
|
{% if form.domain.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.domain.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Custom domain for this tenant (optional)</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="{{ form.description.id_for_label }}" class="form-label">
|
|
Description
|
|
</label>
|
|
{{ form.description }}
|
|
{% if form.description.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.description.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Brief description of the organization</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contact Information -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-address-book me-2"></i>Contact Information
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.contact_email.id_for_label }}" class="form-label">
|
|
Primary Email
|
|
</label>
|
|
{{ form.contact_email }}
|
|
{% if form.contact_email.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.contact_email.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.contact_phone.id_for_label }}" class="form-label">
|
|
Phone Number
|
|
</label>
|
|
{{ form.contact_phone }}
|
|
{% if form.contact_phone.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.contact_phone.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.website.id_for_label }}" class="form-label">
|
|
Website
|
|
</label>
|
|
{{ form.website }}
|
|
{% if form.website.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.website.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.timezone.id_for_label }}" class="form-label">
|
|
Timezone
|
|
</label>
|
|
{{ form.timezone }}
|
|
{% if form.timezone.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.timezone.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="{{ form.address.id_for_label }}" class="form-label">
|
|
Address
|
|
</label>
|
|
{{ form.address }}
|
|
{% if form.address.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.address.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Configuration Settings -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-cogs me-2"></i>Configuration Settings
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.max_users.id_for_label }}" class="form-label">
|
|
Maximum Users
|
|
</label>
|
|
{{ form.max_users }}
|
|
{% if form.max_users.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.max_users.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Leave empty for unlimited users</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.storage_limit_gb.id_for_label }}" class="form-label">
|
|
Storage Limit (GB)
|
|
</label>
|
|
{{ form.storage_limit_gb }}
|
|
{% if form.storage_limit_gb.errors %}
|
|
<div class="invalid-feedback d-block">
|
|
{{ form.storage_limit_gb.errors.0 }}
|
|
</div>
|
|
{% endif %}
|
|
<div class="form-text">Leave empty for unlimited storage</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Features Enabled</label>
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="feature_emr" name="features_enabled" value="EMR">
|
|
<label class="form-check-label" for="feature_emr">
|
|
Electronic Medical Records
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="feature_billing" name="features_enabled" value="BILLING">
|
|
<label class="form-check-label" for="feature_billing">
|
|
Billing & Payments
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="feature_pharmacy" name="features_enabled" value="PHARMACY">
|
|
<label class="form-check-label" for="feature_pharmacy">
|
|
Pharmacy Management
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="feature_lab" name="features_enabled" value="LABORATORY">
|
|
<label class="form-check-label" for="feature_lab">
|
|
Laboratory Services
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="feature_radiology" name="features_enabled" value="RADIOLOGY">
|
|
<label class="form-check-label" for="feature_radiology">
|
|
Radiology & Imaging
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="feature_inventory" name="features_enabled" value="INVENTORY">
|
|
<label class="form-check-label" for="feature_inventory">
|
|
Inventory Management
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="feature_hr" name="features_enabled" value="HR">
|
|
<label class="form-check-label" for="feature_hr">
|
|
Human Resources
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="feature_analytics" name="features_enabled" value="ANALYTICS">
|
|
<label class="form-check-label" for="feature_analytics">
|
|
Analytics & Reporting
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="feature_integration" name="features_enabled" value="INTEGRATION">
|
|
<label class="form-check-label" for="feature_integration">
|
|
External Integrations
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-lg-4">
|
|
<!-- Status & Actions -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-toggle-on me-2"></i>Status & Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<div class="form-check form-switch">
|
|
{{ form.is_active }}
|
|
<label class="form-check-label" for="{{ form.is_active.id_for_label }}">
|
|
Active Tenant
|
|
</label>
|
|
</div>
|
|
<div class="form-text">
|
|
{% if object %}
|
|
Deactivating will prevent users from accessing the system
|
|
{% else %}
|
|
New tenants are active by default
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{% if object %}
|
|
<div class="mb-3">
|
|
<label class="form-label text-muted">Tenant ID</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" value="{{ object.tenant_id }}" readonly>
|
|
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('{{ object.tenant_id }}')">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save me-2"></i>
|
|
{% if object %}Update Tenant{% else %}Create Tenant{% endif %}
|
|
</button>
|
|
<a href="{% if object %}{% url 'core:tenant_detail' object.pk %}{% else %}{% url 'core:tenant_list' %}{% endif %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-times me-2"></i>Cancel
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Help & Guidelines -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-question-circle me-2"></i>Help & Guidelines
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="accordion" id="helpAccordion">
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#helpNaming">
|
|
Naming Guidelines
|
|
</button>
|
|
</h2>
|
|
<div id="helpNaming" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<ul class="mb-0">
|
|
<li>Use clear, descriptive names</li>
|
|
<li>Organization name should be unique</li>
|
|
<li>Display name is shown to users</li>
|
|
<li>Avoid special characters in names</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#helpDomain">
|
|
Domain Configuration
|
|
</button>
|
|
</h2>
|
|
<div id="helpDomain" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<ul class="mb-0">
|
|
<li>Domain is optional but recommended</li>
|
|
<li>Use format: subdomain.yourdomain.com</li>
|
|
<li>Ensure DNS is properly configured</li>
|
|
<li>SSL certificate required for custom domains</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#helpFeatures">
|
|
Feature Selection
|
|
</button>
|
|
</h2>
|
|
<div id="helpFeatures" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
|
<div class="accordion-body">
|
|
<ul class="mb-0">
|
|
<li>Select only needed features</li>
|
|
<li>Features can be changed later</li>
|
|
<li>Some features may require additional setup</li>
|
|
<li>Contact support for custom features</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
// Form validation and enhancement
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Add Bootstrap validation classes
|
|
const form = document.querySelector('form');
|
|
const inputs = form.querySelectorAll('input, select, textarea');
|
|
|
|
inputs.forEach(input => {
|
|
if (!input.classList.contains('form-control') && !input.classList.contains('form-select') && !input.classList.contains('form-check-input')) {
|
|
if (input.type === 'checkbox') {
|
|
input.classList.add('form-check-input');
|
|
} else if (input.tagName === 'SELECT') {
|
|
input.classList.add('form-select');
|
|
} else {
|
|
input.classList.add('form-control');
|
|
}
|
|
}
|
|
|
|
// Add validation feedback
|
|
if (input.hasAttribute('required')) {
|
|
input.addEventListener('invalid', function() {
|
|
input.classList.add('is-invalid');
|
|
});
|
|
|
|
input.addEventListener('input', function() {
|
|
if (input.validity.valid) {
|
|
input.classList.remove('is-invalid');
|
|
input.classList.add('is-valid');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Auto-generate display name from organization name
|
|
const nameInput = document.getElementById('{{ form.name.id_for_label }}');
|
|
const displayNameInput = document.getElementById('{{ form.display_name.id_for_label }}');
|
|
|
|
if (nameInput && displayNameInput && !displayNameInput.value) {
|
|
nameInput.addEventListener('input', function() {
|
|
if (!displayNameInput.value || displayNameInput.value === nameInput.dataset.previousValue) {
|
|
displayNameInput.value = nameInput.value;
|
|
}
|
|
nameInput.dataset.previousValue = nameInput.value;
|
|
});
|
|
}
|
|
|
|
// Load existing features
|
|
{% if object and object.features_enabled %}
|
|
const enabledFeatures = {{ object.features_enabled|safe }};
|
|
enabledFeatures.forEach(feature => {
|
|
const checkbox = document.querySelector(`input[value="${feature}"]`);
|
|
if (checkbox) {
|
|
checkbox.checked = true;
|
|
}
|
|
});
|
|
{% endif %}
|
|
});
|
|
|
|
function copyToClipboard(text) {
|
|
navigator.clipboard.writeText(text).then(function() {
|
|
const button = event.target.closest('button');
|
|
const originalIcon = button.innerHTML;
|
|
button.innerHTML = '<i class="fas fa-check"></i>';
|
|
button.classList.add('btn-success');
|
|
button.classList.remove('btn-outline-secondary');
|
|
|
|
setTimeout(() => {
|
|
button.innerHTML = originalIcon;
|
|
button.classList.remove('btn-success');
|
|
button.classList.add('btn-outline-secondary');
|
|
}, 2000);
|
|
});
|
|
}
|
|
|
|
// Form submission with loading state
|
|
document.querySelector('form').addEventListener('submit', function() {
|
|
const submitBtn = document.querySelector('button[type="submit"]');
|
|
const originalText = submitBtn.innerHTML;
|
|
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
|
|
submitBtn.disabled = true;
|
|
|
|
// Re-enable if form validation fails
|
|
setTimeout(() => {
|
|
if (document.querySelector('.is-invalid')) {
|
|
submitBtn.innerHTML = originalText;
|
|
submitBtn.disabled = false;
|
|
}
|
|
}, 100);
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.form-check-input:checked {
|
|
background-color: #0d6efd;
|
|
border-color: #0d6efd;
|
|
}
|
|
|
|
.accordion-button:not(.collapsed) {
|
|
background-color: #e7f1ff;
|
|
color: #0d6efd;
|
|
}
|
|
|
|
.is-invalid {
|
|
border-color: #dc3545;
|
|
}
|
|
|
|
.is-valid {
|
|
border-color: #198754;
|
|
}
|
|
|
|
.invalid-feedback {
|
|
display: block;
|
|
width: 100%;
|
|
margin-top: 0.25rem;
|
|
font-size: 0.875em;
|
|
color: #dc3545;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|