HH/templates/projects/project_form.html
2026-07-11 19:24:28 +03:00

458 lines
17 KiB
HTML

{% extends "layouts/base.html" %}
{% load i18n %}{% get_current_language as LANG %}
{% block title %}{% if is_create %}{% trans "Create QI Project" %}{% else %}{% trans "Edit QI Project" %}{% endif %} - PX360{% endblock %}
{% block extra_css %}
<style>
.page-header-gradient {
background: linear-gradient(135deg, #005696 0%, #0069a8 50%, #007bbd 100%);
color: white;
padding: 1.5rem 2rem;
border-radius: 1rem;
margin-bottom: 1.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 86, 150, 0.2);
}
.form-section {
background: #fff;
border: 2px solid #e2e8f0;
border-radius: 1rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
transition: all 0.3s ease;
}
.form-section:hover {
border-color: #005696;
box-shadow: 0 4px 12px rgba(0, 86, 150, 0.1);
}
.form-label {
display: block;
font-size: 0.875rem;
font-weight: 600;
color: #1e293b;
margin-bottom: 0.5rem;
}
.form-control, .form-select {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid #e2e8f0;
border-radius: 0.75rem;
font-size: 0.875rem;
transition: all 0.2s ease;
}
.form-control:focus, .form-select:focus {
outline: none;
border-color: #005696;
box-shadow: 0 0 0 3px rgba(0, 86, 150, 0.1);
}
.hh-btn-primary {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
background: #005696;
color: white;
border-radius: 0.75rem;
font-weight: 600;
transition: all 0.2s ease;
border: none;
cursor: pointer;
}
.hh-btn-primary:hover {
background: #007bbd;
}
.hh-btn-secondary {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
background: white;
color: #64748b;
border: 2px solid #e2e8f0;
border-radius: 0.75rem;
font-weight: 600;
transition: all 0.2s ease;
}
.hh-btn-secondary:hover {
background: #f1f5f9;
border-color: #005696;
}
</style>
{% endblock %}
{% block content %}
<!-- Back Button -->
<div class="mb-6">
<a href="{% url 'projects:project_list' %}" class="inline-flex items-center gap-2 text-slate hover:text-navy transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
<span class="text-sm font-medium">{% trans "Back to Projects" %}</span>
</a>
</div>
<!-- Header -->
<div class="page-header-gradient">
<div class="flex justify-between items-start">
<div>
<h1 class="text-2xl font-bold flex items-center gap-3">
<i data-lucide="kanban" class="w-6 h-6"></i>
{% if is_create %}{% trans "Create QI Project" %}{% else %}{% trans "Edit QI Project" %}{% endif %}
</h1>
<p class="text-sm mt-1 opacity-90">
{% if is_create %}
{% trans "Start a new quality improvement initiative" %}
{% else %}
{% trans "Update project details and settings" %}
{% endif %}
</p>
</div>
</div>
</div>
<!-- Form Section -->
<div class="form-section">
<h2 class="text-sm font-bold text-navy mb-4">{% trans "Project Information" %}</h2>
{% if template %}
<!-- Template Preview -->
<div class="mb-6 bg-blue-50 border border-blue-100 rounded-xl p-4">
<div class="flex items-start gap-3">
<i data-lucide="copy" class="w-5 h-5 text-blue mt-0.5"></i>
<div class="flex-1">
<h3 class="text-sm font-bold text-navy mb-1">
{% trans "Creating from Template:" %} {{ template.name }}
</h3>
{% if template.tasks.exists %}
<p class="text-xs text-slate mb-2">
{% trans "This template includes" %} {{ template.tasks.count }} {% trans "task(s) that will be copied to your new project." %}
</p>
<ul class="text-xs text-slate space-y-1">
{% for task in template.tasks.all|slice:":5" %}
<li class="flex items-center gap-2">
<i data-lucide="check" class="w-3 h-3 text-green-600"></i>
{{ task.title }}
</li>
{% endfor %}
{% if template.tasks.count > 5 %}
<li class="text-slate-500 italic">
{% trans "and" %} {{ template.tasks.count|add:"-5" }} {% trans "more..." %}
</li>
{% endif %}
</ul>
{% else %}
<p class="text-xs text-slate">{% trans "No predefined tasks in this template." %}</p>
{% endif %}
</div>
</div>
</div>
{% endif %}
<form method="post" novalidate>
{% csrf_token %}
{% if template %}
<input type="hidden" name="template_id" value="{{ template.pk }}">
{% endif %}
{% if related_model %}
<input type="hidden" name="related_model" value="{{ related_model }}">
{% endif %}
{% if related_id %}
<input type="hidden" name="related_id" value="{{ related_id }}">
{% endif %}
<div class="grid grid-cols-2 gap-6">
<!-- Project Name -->
<div>
<label for="{{ form.name.id_for_label }}" class="form-label">
{% trans "Project Name" %} <span class="text-red-500">*</span>
</label>
{{ form.name }}
{% if form.name.errors %}
<p class="text-red-500 text-xs mt-1">{{ form.name.errors.0 }}</p>
{% endif %}
</div>
<!-- Project Name (Arabic) -->
<div>
<label for="{{ form.name_ar.id_for_label }}" class="form-label">
{% trans "Project Name (Arabic)" %}
</label>
{{ form.name_ar }}
{% if form.name_ar.errors %}
<p class="text-red-500 text-xs mt-1">{{ form.name_ar.errors.0 }}</p>
{% endif %}
</div>
</div>
<!-- Description -->
<div class="mt-6">
<label for="{{ form.description.id_for_label }}" class="form-label">
{% trans "Description" %} <span class="text-red-500">*</span>
</label>
{{ form.description }}
{% if form.description.errors %}
<p class="text-red-500 text-xs mt-1">{{ form.description.errors.0 }}</p>
{% endif %}
<p class="text-xs text-slate mt-1">{% trans "Describe the project objectives and scope" %}</p>
</div>
<div class="grid grid-cols-2 gap-6 mt-6">
<!-- Hospital (hidden, auto-set) -->
{% if not form.hospital.is_hidden %}
<div>
<label for="{{ form.hospital.id_for_label }}" class="form-label">
{% trans "Hospital" %} <span class="text-red-500">*</span>
</label>
{{ form.hospital }}
{% if form.hospital.errors %}
<p class="text-red-500 text-xs mt-1">{{ form.hospital.errors.0 }}</p>
{% endif %}
</div>
{% else %}
{{ form.hospital }}
{% endif %}
<!-- Departments (multi-select) -->
<div class="md:col-span-2">
<label for="{{ form.departments.id_for_label }}" class="form-label">
{% trans "Departments" %}
</label>
{{ form.departments }}
{% if form.departments.errors %}
<p class="text-red-500 text-xs mt-1">{{ form.departments.errors.0 }}</p>
{% endif %}
<p class="text-xs text-slate mt-1">
{% trans "Select one or more departments, then choose team members below." %}
</p>
</div>
</div>
<div class="grid grid-cols-2 gap-6 mt-6">
<!-- Project Lead -->
<div>
<label for="{{ form.project_lead.id_for_label }}" class="form-label">
{% trans "Project Lead" %} <span class="text-red-500">*</span>
</label>
<p class="text-xs text-slate mb-1">{% trans "Must be a manager/champion of a selected department, or PX Management/Employee." %}</p>
{{ form.project_lead }}
{% if form.project_lead.errors %}
<p class="text-red-500 text-xs mt-1">{{ form.project_lead.errors.0 }}</p>
{% endif %}
</div>
<!-- Status -->
<div>
<label for="{{ form.status.id_for_label }}" class="form-label">
{% trans "Status" %} <span class="text-red-500">*</span>
</label>
{{ form.status }}
{% if form.status.errors %}
<p class="text-red-500 text-xs mt-1">{{ form.status.errors.0 }}</p>
{% endif %}
</div>
</div>
<!-- Team Members (explicit per-department selection) -->
<div class="mt-6">
<label class="form-label">
{% trans "Team Members" %}
</label>
<p class="text-xs text-slate mb-2">
{% trans "For each selected department, pick which role-holder(s) join the team. The project lead is always included automatically." %}
</p>
<div id="teamPanel" class="space-y-3"></div>
{% if form.team_members.errors %}
<p class="text-red-500 text-xs mt-2">{{ form.team_members.errors.0 }}</p>
{% endif %}
</div>
<div class="grid grid-cols-2 gap-6 mt-6">
<!-- Start Date -->
<div>
<label for="{{ form.start_date.id_for_label }}" class="form-label">
{% trans "Start Date" %}
</label>
{{ form.start_date }}
{% if form.start_date.errors %}
<p class="text-red-500 text-xs mt-1">{{ form.start_date.errors.0 }}</p>
{% endif %}
</div>
<!-- Target Completion Date -->
<div>
<label for="{{ form.target_completion_date.id_for_label }}" class="form-label">
{% trans "Target Completion Date" %}
</label>
{{ form.target_completion_date }}
{% if form.target_completion_date.errors %}
<p class="text-red-500 text-xs mt-1">{{ form.target_completion_date.errors.0 }}</p>
{% endif %}
</div>
</div>
<!-- Outcome Description (only show for existing projects) -->
{% if not is_create %}
<div class="mt-6">
<label for="{{ form.outcome_description.id_for_label }}" class="form-label">
{% trans "Outcome Description" %}
</label>
{{ form.outcome_description }}
{% if form.outcome_description.errors %}
<p class="text-red-500 text-xs mt-1">{{ form.outcome_description.errors.0 }}</p>
{% endif %}
<p class="text-xs text-slate mt-1">{% trans "Document project outcomes and results" %}</p>
</div>
{% endif %}
<!-- Submit Buttons -->
<div class="flex items-center gap-3 mt-8 pt-6 border-t">
<button type="submit" class="hh-btn hh-btn-primary">
<i data-lucide="save" class="w-4 h-4"></i>
{% if is_create %}{% trans "Create Project" %}{% else %}{% trans "Save Changes" %}{% endif %}
</button>
<a href="{% url 'projects:project_list' %}" class="hh-btn hh-btn-secondary">
<i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel" %}
</a>
</div>
</form>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const departmentsSelect = document.getElementById('{{ form.departments.id_for_label }}');
const projectLeadSelect = document.getElementById('{{ form.project_lead.id_for_label }}');
const teamPanel = document.getElementById('teamPanel');
const initialTeamMemberIds = {{ team_member_ids_json|default:"[]" }};
const managerLabel = "{% trans 'Manager' %}";
const championLabel = "{% trans 'Champion' %}";
const emptyDeptsText = "{% trans 'Select departments first to choose team members.' %}";
const noCandidatesText = "{% trans 'No manager or champion assigned to this department.' %}";
let checkedTeamIds = new Set(initialTeamMemberIds);
function getSelectedDeptIds() {
if (departmentsSelect && departmentsSelect.tomselect) {
const v = departmentsSelect.tomselect.getValue();
return Array.isArray(v) ? v.filter(Boolean) : (v ? [v] : []);
}
if (!departmentsSelect) return [];
return Array.from(departmentsSelect.selectedOptions || []).map(o => o.value).filter(Boolean);
}
function refreshTeamAndLeads() {
const deptIds = getSelectedDeptIds();
if (!deptIds.length) {
renderTeamPanel([]);
renderLeadOptions([]);
return;
}
const url = '{% url "projects:department_team_options" %}?dept_ids=' + encodeURIComponent(deptIds.join(','));
fetch(url)
.then(r => r.json())
.then(data => {
renderTeamPanel(data.departments || []);
renderLeadOptions(data.leads || []);
})
.catch(() => {});
}
function renderTeamPanel(departments) {
if (!teamPanel) return;
teamPanel.innerHTML = '';
if (!departments.length) {
const p = document.createElement('p');
p.className = 'text-xs text-slate-400 italic';
p.textContent = emptyDeptsText;
teamPanel.appendChild(p);
return;
}
departments.forEach(dept => {
const card = document.createElement('div');
card.className = 'border border-slate-200 rounded-xl p-3';
const title = document.createElement('p');
title.className = 'text-xs font-bold text-navy mb-2';
title.textContent = dept.name;
card.appendChild(title);
const roles = [];
if (dept.manager) roles.push({staff: dept.manager, label: managerLabel});
if (dept.champion) roles.push({staff: dept.champion, label: championLabel});
if (!roles.length) {
const none = document.createElement('p');
none.className = 'text-xs text-slate-400';
none.textContent = noCandidatesText;
card.appendChild(none);
} else {
roles.forEach(r => {
const wrap = document.createElement('label');
wrap.className = 'flex items-center gap-2 py-1 cursor-pointer';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.name = 'team_members';
cb.value = r.staff.id;
cb.className = 'rounded border-slate-300 text-navy focus:ring-navy/20';
if (checkedTeamIds.has(String(r.staff.id))) cb.checked = true;
cb.addEventListener('change', function() {
if (this.checked) checkedTeamIds.add(String(r.staff.id));
else checkedTeamIds.delete(String(r.staff.id));
});
const lbl = document.createElement('span');
lbl.className = 'text-sm text-slate-700';
lbl.textContent = r.label + ': ' + r.staff.name;
wrap.appendChild(cb);
wrap.appendChild(lbl);
card.appendChild(wrap);
});
}
teamPanel.appendChild(card);
});
}
function renderLeadOptions(leads) {
if (!projectLeadSelect) return;
let previous;
if (projectLeadSelect.tomselect) {
previous = projectLeadSelect.tomselect.getValue();
} else {
previous = projectLeadSelect.value;
}
if (projectLeadSelect.tomselect) {
const ts = projectLeadSelect.tomselect;
ts.clear();
ts.clearOptions();
leads.forEach(l => ts.addOption({value: l.id, text: l.label}));
if (previous && leads.some(l => l.id === previous)) {
ts.setValue(previous, true);
}
} else {
projectLeadSelect.innerHTML = '<option value="">---------</option>';
leads.forEach(l => {
const opt = document.createElement('option');
opt.value = l.id;
opt.textContent = l.label;
if (String(l.id) === String(previous)) opt.selected = true;
projectLeadSelect.appendChild(opt);
});
}
}
// Listen for department changes (TomSelect or native)
if (departmentsSelect && departmentsSelect.tomselect) {
departmentsSelect.tomselect.on('change', refreshTeamAndLeads);
} else if (departmentsSelect) {
departmentsSelect.addEventListener('change', refreshTeamAndLeads);
}
// Initial population
refreshTeamAndLeads();
});
</script>
{% endblock %}