Added new templates for project management functionality including listing, creation, and detailed views. These templates integrate with HTMX for dynamic interactions and provide a responsive layout. **New Features** - **project_list.html**: Displays a table of projects with status badges, client associations, and deadline tracking. Includes actions for creating, editing, and deleting projects. - **project_detail.html**: Shows detailed project information including status, description, client, and deadline. Features an embedded kanban board for task management and a modal for task creation. - **project_form.html**: Provides a multi-step form wizard for creating/editing projects with validation and navigation controls. **Enhancements & Refinements** - Responsive design across all templates for consistent UX on different devices. - Dynamic status badges color-coded based on project state. - Integrated HTMX for seamless form submissions and DOM updates in project list and task management.
106 lines
4.1 KiB
HTML
106 lines
4.1 KiB
HTML
{% extends "shared/base.html" %}
|
|
{% load static %}
|
|
{% block content %}
|
|
{% include "shared/navbar.html" %}
|
|
|
|
<div class="pt-24 px-4 max-w-7xl mx-auto min-h-screen flex flex-col pb-10">
|
|
<h2 class="text-2xl font-bold mb-6">{% if project %}Edit Project{% else %}New Project{% endif %}</h2>
|
|
{% include "projects/partials/project_form.html" %}
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
const stepper = document.getElementById('wizard-validation-horizontal');
|
|
if (!stepper) return;
|
|
|
|
const navItems = stepper.querySelectorAll('[data-stepper-nav-item]');
|
|
const contentItems = stepper.querySelectorAll('[data-stepper-content-item]');
|
|
const backBtn = stepper.querySelector('[data-stepper-back-btn]');
|
|
const nextBtn = stepper.querySelector('[data-stepper-next-btn]');
|
|
const finishBtn = stepper.querySelector('[data-stepper-finish-btn]');
|
|
let currentStep = 1;
|
|
const totalSteps = 3;
|
|
|
|
function updateStepper() {
|
|
// Update Nav
|
|
navItems.forEach(item => {
|
|
const index = parseInt(JSON.parse(item.dataset.stepperNavItem).index);
|
|
const circle = item.querySelector('span > span');
|
|
const checkIcon = circle.querySelector('.icon-\\[tabler--check\\]');
|
|
const numberSpan = circle.querySelector('span:not([class*="icon"])');
|
|
|
|
// Reset classes
|
|
circle.classList.remove('bg-brand', 'text-white', 'bg-success', 'text-white');
|
|
circle.classList.add('text-bg-soft-neutral');
|
|
checkIcon.classList.add('hidden');
|
|
numberSpan.classList.remove('hidden');
|
|
|
|
if (index < currentStep) {
|
|
// Completed
|
|
circle.classList.remove('text-bg-soft-neutral');
|
|
circle.classList.add('bg-success', 'text-white');
|
|
checkIcon.classList.remove('hidden');
|
|
numberSpan.classList.add('hidden');
|
|
} else if (index === currentStep) {
|
|
// Active
|
|
circle.classList.remove('text-bg-soft-neutral');
|
|
circle.classList.add('bg-brand', 'text-white');
|
|
}
|
|
});
|
|
|
|
// Update Content
|
|
contentItems.forEach(item => {
|
|
const index = parseInt(JSON.parse(item.dataset.stepperContentItem).index);
|
|
if (index === currentStep) {
|
|
item.style.display = 'block';
|
|
} else {
|
|
item.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// Update Buttons
|
|
if (currentStep === 1) {
|
|
backBtn.style.display = 'none';
|
|
nextBtn.style.display = 'inline-flex';
|
|
finishBtn.style.display = 'none';
|
|
} else if (currentStep === totalSteps) {
|
|
backBtn.style.display = 'inline-flex';
|
|
nextBtn.style.display = 'none';
|
|
finishBtn.style.display = 'inline-flex';
|
|
} else {
|
|
backBtn.style.display = 'inline-flex';
|
|
nextBtn.style.display = 'inline-flex';
|
|
finishBtn.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
nextBtn.addEventListener('click', () => {
|
|
// Simple validation check for current step inputs
|
|
const currentContent = stepper.querySelector(`[data-stepper-content-item='{ "index": ${currentStep} }']`);
|
|
const inputs = currentContent.querySelectorAll('input, textarea, select');
|
|
let valid = true;
|
|
inputs.forEach(input => {
|
|
if (!input.checkValidity()) {
|
|
valid = false;
|
|
input.reportValidity();
|
|
}
|
|
});
|
|
|
|
if (valid && currentStep < totalSteps) {
|
|
currentStep++;
|
|
updateStepper();
|
|
}
|
|
});
|
|
|
|
backBtn.addEventListener('click', () => {
|
|
if (currentStep > 1) {
|
|
currentStep--;
|
|
updateStepper();
|
|
}
|
|
});
|
|
|
|
// Initialize
|
|
updateStepper();
|
|
})();
|
|
</script>
|
|
{% endblock %} |