ATS/templates/forms/form_builder.html
2026-02-01 13:38:06 +03:00

1252 lines
70 KiB
HTML

{% load static %}
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ATS Form Builder</title>
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
temple: {
red: '#981e32',
light: '#c5283e',
dark: '#7a1426',
}
}
}
}
}
</script>
</head>
<body class="h-full bg-gray-50 text-gray-900 overflow-hidden">
<!-- Pass Django CSRF token and other data -->
<script>
const djangoConfig = {
csrfToken: "{{ csrf_token }}",
saveUrl: "{% url 'save_form_template' %}",
loadUrl: {% if template_slug %}"{% url 'load_form_template' job_slug %}"{% else %}null{% endif %},
templateId: {% if template_slug %}'{{ template_slug }}'{% else %}null{% endif %},
jobId: {% if job_id %}{{ job_id }}{% else %}null{% endif %}
};
</script>
<div class="flex h-full">
<!-- Sidebar with form elements -->
<div class="w-[280px] bg-white border-r border-gray-200 flex flex-col">
<div class="p-5 pb-5 mb-5 border-b border-gray-200 flex-shrink-0">
<a href="{% url 'form_templates_list' %}" class="block text-lg font-bold text-temple-red flex items-center gap-2">
<i data-lucide="layout" class="w-6 h-6"></i>
Form Builder
</a>
</div>
<div class="flex-1 overflow-y-auto px-5">
<!-- Personal Information Category -->
<div class="mb-6">
<h3 class="text-sm font-semibold text-gray-600 mb-3 flex items-center gap-2">
<i data-lucide="user" class="w-4 h-4"></i> Personal Information
</h3>
<div class="space-y-2">
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="text" data-label="First Name">
<div class="flex items-center gap-2">
<i data-lucide="type" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Text Input</span>
</div>
</div>
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="email" data-label="Email">
<div class="flex items-center gap-2">
<i data-lucide="mail" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Email</span>
</div>
</div>
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="phone" data-label="Phone">
<div class="flex items-center gap-2">
<i data-lucide="phone" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Phone</span>
</div>
</div>
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="text" data-label="Address">
<div class="flex items-center gap-2">
<i data-lucide="map-pin" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Address</span>
</div>
</div>
</div>
</div>
<!-- Application Details Category -->
<div class="mb-6">
<h3 class="text-sm font-semibold text-gray-600 mb-3 flex items-center gap-2">
<i data-lucide="file-text" class="w-4 h-4"></i> Application Details
</h3>
<div class="space-y-2">
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="textarea" data-label="Cover Letter">
<div class="flex items-center gap-2">
<i data-lucide="align-left" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Text Area</span>
</div>
</div>
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="file" data-label="Resume">
<div class="flex items-center gap-2">
<i data-lucide="upload-cloud" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">File Upload</span>
</div>
</div>
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="date" data-label="Available Date">
<div class="flex items-center gap-2">
<i data-lucide="calendar" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Date Picker</span>
</div>
</div>
</div>
</div>
<!-- Selection Fields Category -->
<div class="mb-6">
<h3 class="text-sm font-semibold text-gray-600 mb-3 flex items-center gap-2">
<i data-lucide="list" class="w-4 h-4"></i> Selection Fields
</h3>
<div class="space-y-2">
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="select" data-label="Position">
<div class="flex items-center gap-2">
<i data-lucide="chevron-down" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Dropdown</span>
</div>
</div>
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="radio" data-label="Experience Level">
<div class="flex items-center gap-2">
<i data-lucide="circle-dot" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Radio Buttons</span>
</div>
</div>
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="checkbox" data-label="Skills">
<div class="flex items-center gap-2">
<i data-lucide="square-check" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Checkboxes</span>
</div>
</div>
</div>
</div>
<!-- Professional Fields Category -->
<div class="mb-6">
<h3 class="text-sm font-semibold text-gray-600 mb-3 flex items-center gap-2">
<i data-lucide="briefcase" class="w-4 h-4"></i> Professional Fields
</h3>
<div class="space-y-2">
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="text" data-label="Company">
<div class="flex items-center gap-2">
<i data-lucide="building-2" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Company</span>
</div>
</div>
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="text" data-label="Position">
<div class="flex items-center gap-2">
<i data-lucide="user-tie" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Position</span>
</div>
</div>
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="text" data-label="Degree">
<div class="flex items-center gap-2">
<i data-lucide="graduation-cap" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Degree</span>
</div>
</div>
<div class="field-item bg-gray-50 border border-dashed border-gray-300 rounded-xl p-3 cursor-grab hover:bg-white hover:border-temple-red hover:shadow-md transition-all" draggable="true" data-type="text" data-label="Institution">
<div class="flex items-center gap-2">
<i data-lucide="school" class="w-5 h-5 text-temple-red"></i>
<span class="text-sm font-medium text-gray-700">Institution</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Main Content Area -->
<div class="flex-1 flex flex-col p-5 overflow-hidden">
<!-- Breadcrumb -->
<nav class="mb-4">
<div class="flex items-center gap-2 text-sm text-gray-500 flex-wrap">
<a href="{% url 'dashboard' %}" class="hover:text-temple-red transition">Home</a>
<span class="text-gray-400">/</span>
<a href="{% url 'job_list' %}" class="hover:text-temple-red transition">Jobs</a>
<span class="text-gray-400">/</span>
<a href="{% url 'job_detail' template.job.slug %}" class="hover:text-temple-red transition">Job: ({{template.job.title}})</a>
<span class="text-gray-400">/</span>
<span class="font-semibold text-gray-900">Form Builder</span>
</div>
</nav>
<!-- Toolbar -->
<div class="flex justify-between items-center mb-4 pb-4 border-b border-gray-200 flex-shrink-0">
<h1 id="formTitle" class="text-2xl font-bold text-temple-red">Resume Application Form</h1>
<div class="flex gap-2">
<button class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2 rounded-xl text-sm transition font-medium" id="formSettingsBtn">
<i data-lucide="settings" class="w-4 h-4"></i> Settings
</button>
<button class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2 rounded-xl text-sm transition font-medium" id="addStageBtn">
<i data-lucide="plus" class="w-4 h-4"></i> Add Stage
</button>
<button class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-semibold px-4 py-2 rounded-xl text-sm transition shadow-sm hover:shadow-md" id="saveFormBtn">
<i data-lucide="save" class="w-4 h-4"></i> Save Form
</button>
</div>
</div>
<!-- Stage Navigation -->
<div class="h-[100px] overflow-x-auto overflow-y-hidden p-1.5 mb-4 flex-shrink-0 bg-white rounded-xl shadow-sm">
<div class="stage-nav flex min-w-full gap-1 px-2.5 py-0 whitespace-nowrap h-[70px]" id="stageNav"></div>
</div>
<!-- Form Builder and Editor Container -->
<div class="flex gap-5 flex-1 overflow-hidden">
<!-- Form Builder Area -->
<div class="flex-1 bg-white rounded-xl shadow-sm flex flex-col overflow-hidden">
<div class="p-5 border-b border-gray-200 flex justify-between items-center flex-shrink-0">
<h3 id="currentStageTitle" class="text-lg font-bold text-temple-red flex items-center gap-2">
<i data-lucide="list" class="w-5 h-5"></i>
<span id="stageNameDisplay">Contact Information</span> Stage
<span class="required-indicator text-red-500 font-bold" id="stageRequiredIndicator" style="display: none;"> *</span>
<span class="predefined-badge bg-emerald-500 text-white text-xs px-2 py-0.5 rounded-full" id="stagePredefinedBadge" style="display: none;">Default</span>
</h3>
<button class="p-1 text-gray-400 hover:text-temple-red hover:bg-gray-100 rounded-lg transition" id="renameStageBtn" title="Rename stage">
<i data-lucide="edit-3" class="w-4 h-4"></i>
</button>
</div>
<div class="p-5 min-h-[300px] flex-1 overflow-y-auto" id="formStage">
<div class="empty-state text-center py-10 text-gray-500" id="emptyState">
<i data-lucide="cloud-upload" class="w-12 h-12 mx-auto mb-4 text-gray-300"></i>
<p>Drag form elements here to build your stage</p>
</div>
</div>
</div>
<!-- Field Editor Panel -->
<div class="w-[320px] bg-white rounded-xl shadow-sm p-5 flex flex-col flex-shrink-0 transition-all" id="fieldEditor" style="display: none;">
<div class="flex justify-between items-center mb-5 pb-4 border-b border-gray-200 flex-shrink-0">
<h3 class="text-lg font-bold text-temple-red flex items-center gap-2">
<i data-lucide="sliders-horizontal" class="w-5 h-5"></i> Field Properties
</h3>
<button class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-3 py-1.5 rounded-lg text-sm transition" id="closeEditorBtn">Close</button>
</div>
<div class="flex-1 overflow-y-auto">
<div class="mb-5">
<label class="block text-sm font-semibold text-gray-700 mb-2">Field Label</label>
<input type="text" id="fieldLabel" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" placeholder="Enter field label">
</div>
<div class="mb-5" id="placeholderGroup">
<label class="block text-sm font-semibold text-gray-700 mb-2">Placeholder</label>
<input type="text" id="fieldPlaceholder" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" placeholder="Enter placeholder text">
</div>
<div class="mb-5">
<div class="flex items-center gap-3">
<input type="checkbox" id="requiredField" class="w-4 h-4 text-temple-red border-gray-300 rounded focus:ring-2 focus:ring-temple-red/20">
<label for="requiredField" class="text-sm font-medium text-gray-700 cursor-pointer">Required Field</label>
</div>
</div>
</div>
<!-- Options Editor -->
<div class="mt-5 pt-5 border-t border-gray-200" id="optionsEditor" style="display: none;">
<h4 class="text-sm font-semibold text-gray-600 mb-3 flex items-center gap-2">
<i data-lucide="list" class="w-4 h-4"></i> Options
</h4>
<div class="options-list space-y-2" id="optionsList"></div>
<button class="add-option text-temple-red cursor-pointer text-sm font-medium inline-flex items-center gap-2 mt-3" id="addOptionBtn">
<i data-lucide="plus" class="w-4 h-4"></i> Add Option
</button>
</div>
<!-- File Settings -->
<div class="mt-5 pt-5 border-t border-gray-200" id="fileSettings" style="display: none;">
<h4 class="text-sm font-semibold text-gray-600 mb-3 flex items-center gap-2">
<i data-lucide="file" class="w-4 h-4"></i> File Settings
</h4>
<div class="mb-4">
<label class="block text-sm font-semibold text-gray-700 mb-2">Allowed File Types</label>
<input type="text" id="fileTypes" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" placeholder=".pdf, .doc, .docx">
</div>
<div class="mb-4">
<label class="block text-sm font-semibold text-gray-700 mb-2">Max File Size (MB)</label>
<input type="number" id="maxFileSize" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" min="1" max="100" value="5">
</div>
<div class="mb-4">
<div class="flex items-center gap-3">
<input type="checkbox" id="multipleFiles" class="w-4 h-4 text-temple-red border-gray-300 rounded focus:ring-2 focus:ring-temple-red/20">
<label for="multipleFiles" class="text-sm font-medium text-gray-700 cursor-pointer">Allow Multiple Files</label>
</div>
<p class="text-xs text-gray-500 mt-1">Enable this to allow uploading multiple files for this field.</p>
</div>
<div class="mb-4">
<label class="block text-sm font-semibold text-gray-700 mb-2">Maximum Number of Files</label>
<input type="number" id="maxFiles" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition bg-gray-50" min="1" max="10" value="1" disabled>
<p class="text-xs text-gray-500 mt-1">Only applicable when multiple files are allowed.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Stage Rename Modal -->
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 opacity-0 invisible transition-opacity" id="renameModal">
<div class="bg-white rounded-xl p-6 w-[400px] max-w-[90%] shadow-2xl transform scale-90 transition-transform">
<div class="flex justify-between items-center mb-5">
<h3 class="text-xl font-bold text-temple-red">Rename Stage</h3>
<button class="text-gray-400 hover:text-gray-600 transition text-2xl" id="closeRenameModal">&times;</button>
</div>
<div class="mb-5">
<label class="block text-sm font-semibold text-gray-700 mb-2">Stage Name</label>
<input type="text" id="stageName" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" placeholder="Enter stage name">
</div>
<div class="flex justify-end gap-2">
<button class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2 rounded-xl text-sm transition font-medium" id="cancelRenameBtn">Cancel</button>
<button class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-semibold px-4 py-2 rounded-xl text-sm transition shadow-sm" id="saveRenameBtn">Save</button>
</div>
</div>
</div>
<!-- Form Settings Modal -->
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 opacity-0 invisible transition-opacity" id="formSettingsModal">
<div class="bg-white rounded-xl p-6 w-[500px] max-w-[90%] shadow-2xl transform scale-90 transition-transform">
<div class="flex justify-between items-center mb-5">
<h3 class="text-xl font-bold text-temple-red">Form Settings</h3>
<button class="text-gray-400 hover:text-gray-600 transition text-2xl" id="closeFormSettingsModal">&times;</button>
</div>
<div class="space-y-4 mb-5">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Form Name</label>
<input type="text" id="formName" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" placeholder="Enter form name">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Form Description</label>
<textarea id="formDescription" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition resize-none" rows="3" placeholder="Enter form description"></textarea>
</div>
<div>
<div class="flex items-center gap-3">
<input type="checkbox" id="formActive" class="w-4 h-4 text-temple-red border-gray-300 rounded focus:ring-2 focus:ring-temple-red/20">
<label for="formActive" class="text-sm font-medium text-gray-700 cursor-pointer">Active Form</label>
</div>
<p class="text-xs text-gray-500 mt-1">When active, this form will be available for applicants to fill out.</p>
</div>
</div>
<div class="flex justify-end gap-2">
<button class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2 rounded-xl text-sm transition font-medium" id="cancelFormSettingsBtn">Cancel</button>
<button class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-semibold px-4 py-2 rounded-xl text-sm transition shadow-sm" id="saveFormSettingsBtn">Save</button>
</div>
</div>
</div>
<script>
// Application State
const state = {
draggedStageIndex: null,
formName: 'Resume Application Form',
formDescription: '',
formActive: true,
stages: [
{
id: 1,
name: 'Contact Information',
predefined: true,
fields: [
{ id: 1, type: 'text', label: 'Full Name', placeholder: 'Enter your full name', required: true, predefined: true },
{ id: 2, type: 'email', label: 'Email Address', placeholder: 'Enter your email', required: true, predefined: true },
{ id: 3, type: 'phone', label: 'Phone Number', placeholder: 'Enter your phone number', required: true, predefined: true },
{ id: 4, type: 'text', label: 'Address', placeholder: 'Enter your address', required: false, predefined: true },
{
id: 26,
type: 'file',
label: 'Resume Upload',
required: true,
predefined: true,
fileTypes: '.pdf,.doc,.docx',
maxFileSize: 5,
uploadedFile: null
}
]
},
{
id: 2,
name: 'Resume Objective',
predefined: true,
fields: [
{ id: 5, type: 'textarea', label: 'Career Objective', placeholder: 'Describe your career goals and what you hope to achieve', required: false, predefined: true }
]
},
{
id: 3,
name: 'Education',
predefined: true,
fields: [
{ id: 6, type: 'text', label: 'Degree', placeholder: 'e.g., Bachelor of Science in Computer Science', required: true, predefined: true },
{ id: 7, type: 'text', label: 'Institution', placeholder: 'Name of your university or school', required: true, predefined: true },
{ id: 8, type: 'text', label: 'Location', placeholder: 'City, State', required: false, predefined: true },
{ id: 9, type: 'date', label: 'Graduation Date', placeholder: 'MM/YYYY', required: false, predefined: true }
]
},
{
id: 4,
name: 'Experience',
predefined: true,
fields: [
{ id: 10, type: 'text', label: 'Position Title', placeholder: 'e.g., Software Engineer', required: true, predefined: true },
{ id: 11, type: 'text', label: 'Company Name', placeholder: 'Name of company', required: true, predefined: true },
{ id: 12, type: 'text', label: 'Location', placeholder: 'City, State', required: false, predefined: true },
{ id: 13, type: 'date', label: 'Start Date', placeholder: 'MM/YYYY', required: true, predefined: true },
{ id: 14, type: 'date', label: 'End Date', placeholder: 'MM/YYYY or Present', required: true, predefined: true },
{ id: 15, type: 'textarea', label: 'Responsibilities & Achievements', placeholder: 'Describe your key responsibilities and achievements', required: false, predefined: true }
]
},
{
id: 5,
name: 'Skills',
predefined: true,
fields: [
{ id: 16, type: 'checkbox', label: 'Technical Skills', options: ['Programming Languages', 'Frameworks', 'Tools & Technologies'], required: false, predefined: true }
]
},
{
id: 6,
name: 'Summary',
predefined: true,
fields: [
{ id: 17, type: 'textarea', label: 'Professional Summary', placeholder: 'Brief overview of your professional background and key qualifications', required: false, predefined: true }
]
},
{
id: 7,
name: 'Certifications',
predefined: true,
fields: [
{ id: 18, type: 'text', label: 'Certification Name', placeholder: 'e.g., AWS Certified Solutions Architect', required: false, predefined: true },
{ id: 19, type: 'text', label: 'Issuing Organization', placeholder: 'Name of certifying body', required: false, predefined: true },
{ id: 20, type: 'date', label: 'Issue Date', placeholder: 'MM/YYYY', required: false, predefined: true },
{ id: 21, type: 'date', label: 'Expiration Date', placeholder: 'MM/YYYY or N/A', required: false, predefined: true }
]
},
{
id: 8,
name: 'Awards and Recognitions',
predefined: true,
fields: [
{ id: 22, type: 'text', label: 'Award Name', placeholder: 'Name of award or recognition', required: false, predefined: true },
{ id: 23, type: 'text', label: 'Issuing Organization', placeholder: 'Who gave you this award?', required: false, predefined: true },
{ id: 24, type: 'date', label: 'Date Received', placeholder: 'MM/YYYY', required: false, predefined: true },
{ id: 25, type: 'textarea', label: 'Description', placeholder: 'Brief description of award and why you received it', required: false, predefined: true }
]
}
],
currentStage: 0,
nextFieldId: 27,
nextStageId: 9,
selectedField: null,
draggedField: null,
draggedFieldIndex: null,
templateId: djangoConfig.templateId
};
// DOM Elements
const elements = {
stageNav: document.getElementById('stageNav'),
formStage: document.getElementById('formStage'),
emptyState: document.getElementById('emptyState'),
fieldEditor: document.getElementById('fieldEditor'),
currentStageTitle: document.getElementById('currentStageTitle'),
stageNameDisplay: document.getElementById('stageNameDisplay'),
stageRequiredIndicator: document.getElementById('stageRequiredIndicator'),
stagePredefinedBadge: document.getElementById('stagePredefinedBadge'),
addStageBtn: document.getElementById('addStageBtn'),
saveFormBtn: document.getElementById('saveFormBtn'),
renameStageBtn: document.getElementById('renameStageBtn'),
renameModal: document.getElementById('renameModal'),
stageName: document.getElementById('stageName'),
closeRenameModal: document.getElementById('closeRenameModal'),
cancelRenameBtn: document.getElementById('cancelRenameBtn'),
saveRenameBtn: document.getElementById('saveRenameBtn'),
fieldLabel: document.getElementById('fieldLabel'),
fieldPlaceholder: document.getElementById('fieldPlaceholder'),
placeholderGroup: document.getElementById('placeholderGroup'),
requiredField: document.getElementById('requiredField'),
optionsEditor: document.getElementById('optionsEditor'),
optionsList: document.getElementById('optionsList'),
addOptionBtn: document.getElementById('addOptionBtn'),
fileSettings: document.getElementById('fileSettings'),
fileTypes: document.getElementById('fileTypes'),
maxFileSize: document.getElementById('maxFileSize'),
multipleFiles: document.getElementById('multipleFiles'),
maxFiles: document.getElementById('maxFiles'),
closeEditorBtn: document.getElementById('closeEditorBtn'),
formTitle: document.getElementById('formTitle'),
formSettingsBtn: document.getElementById('formSettingsBtn'),
formSettingsModal: document.getElementById('formSettingsModal'),
formName: document.getElementById('formName'),
formDescription: document.getElementById('formDescription'),
formActive: document.getElementById('formActive'),
closeFormSettingsModal: document.getElementById('closeFormSettingsModal'),
cancelFormSettingsBtn: document.getElementById('cancelFormSettingsBtn'),
saveFormSettingsBtn: document.getElementById('saveFormSettingsBtn')
};
// Utility Functions
function getFieldIcon(type) {
const icons = {
'text': 'type',
'email': 'mail',
'phone': 'phone',
'textarea': 'align-left',
'file': 'upload-cloud',
'date': 'calendar',
'select': 'chevron-down',
'radio': 'circle-dot',
'checkbox': 'square-check'
};
return icons[type] || 'help-circle';
}
function hasRequiredFields(stage) {
return stage.fields.some(field => field.required);
}
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];
}
// API Functions
async function saveFormTemplate() {
const formData = {
name: state.formName,
description: state.formDescription,
is_active: state.formActive,
template_slug: state.templateId,
stages: state.stages.map(stage => ({
name: stage.name,
predefined: stage.predefined,
fields: stage.fields.map(field => ({
type: field.type,
label: field.label,
placeholder: field.placeholder || '',
required: field.required || false,
options: field.options || [],
fileTypes: field.fileTypes || '',
maxFileSize: field.maxFileSize || 5,
predefined: field.predefined
}))
}))
};
if (djangoConfig.jobId) {
formData.job = djangoConfig.jobId;
}
try {
const response = await fetch(djangoConfig.saveUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': djangoConfig.csrfToken,
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(formData)
});
const result = await response.json();
if (result.success) {
state.templateId = result.template_slug;
window.location.href = "{% url 'job_detail' template.job.slug %}";
} else {
alert('Error saving form template: ' + result.error);
}
} catch (error) {
console.error('Error:', error);
alert('Error saving form template. Please try again.');
}
}
// Load existing template
async function loadExistingTemplate() {
if (djangoConfig.loadUrl) {
try {
const response = await fetch(djangoConfig.loadUrl);
const result = await response.json();
if (result.success) {
const templateData = result.template;
state.formName = templateData.name || 'Untitled Form';
state.formDescription = templateData.description || '';
state.formActive = templateData.is_active !== false;
elements.formTitle.textContent = state.formName;
elements.formName.value = state.formName;
elements.formDescription.value = state.formDescription;
elements.formActive.checked = state.formActive;
state.stages = templateData.stages;
state.templateId = templateData.template_slug;
let maxFieldId = 0;
let maxStageId = 0;
templateData.stages.forEach(stage => {
maxStageId = Math.max(maxStageId, stage.id);
stage.fields.forEach(field => {
maxFieldId = Math.max(maxFieldId, field.id);
});
});
state.nextFieldId = maxFieldId + 1;
state.nextStageId = maxStageId + 1;
state.currentStage = 0;
elements.formStage.style.display = 'block';
elements.emptyState.style.display = 'none';
renderStageNavigation();
renderCurrentStage();
}
} catch (error) {
console.error('Error loading template:', error);
elements.formTitle.textContent = 'Error Loading Template';
elements.emptyState.style.display = 'block';
elements.emptyState.innerHTML = '<i data-lucide="alert-triangle" class="w-12 h-12 mx-auto mb-4 text-red-500"></i><p>Error loading template data.</p>';
elements.formStage.style.display = 'none';
}
}
}
// Render Functions
function renderStageNavigation() {
elements.stageNav.innerHTML = '';
state.stages.forEach((stage, index) => {
const stageTab = document.createElement('div');
stageTab.className = `stage-tab px-5 py-3 bg-gray-50 border border-gray-200 rounded-t-xl mr-1 cursor-pointer whitespace-nowrap transition-all flex items-center gap-2 flex-shrink-0 ${index === state.currentStage ? 'bg-white border-b border-transparent z-10' : 'hover:bg-gray-100'}`;
stageTab.dataset.index = index;
stageTab.draggable = true;
stageTab.addEventListener('dragstart', (e) => startStageDrag(e, index));
stageTab.addEventListener('dragover', allowStageDrop);
stageTab.addEventListener('dragenter', dragStageEnter);
stageTab.addEventListener('dragleave', dragStageLeave);
stageTab.addEventListener('drop', (e) => dropStage(e, index));
stageTab.innerHTML = `
${stage.name}
${hasRequiredFields(stage) ? '<span class="required-indicator text-red-500 font-bold"> *</span>' : ''}
${stage.predefined ? '<span class="predefined-badge bg-emerald-500 text-white text-xs px-2 py-0.5 rounded-full">Default</span>' : ''}
<span class="rename-btn opacity-0 group-hover:opacity-100 transition-opacity">
<i data-lucide="edit-3" class="w-3 h-3"></i>
</span>
${(!stage.predefined || state.stages.length > 1) ? '<span class="remove-btn opacity-0 group-hover:opacity-100 transition-opacity absolute right-2 top-1/2 -translate-y-1/2"><i data-lucide="x" class="w-3 h-3"></i></span>' : ''}
`;
stageTab.addEventListener('click', (e) => {
if (!e.target.closest('.rename-btn') && !e.target.closest('.remove-btn')) {
state.currentStage = index;
renderCurrentStage();
renderStageNavigation();
}
});
const renameBtn = stageTab.querySelector('.rename-btn');
if (renameBtn) {
renameBtn.addEventListener('click', (e) => {
e.stopPropagation();
openRenameModal(stage);
});
}
const removeBtn = stageTab.querySelector('.remove-btn');
if (removeBtn) {
removeBtn.addEventListener('click', (e) => {
e.stopPropagation();
removeStage(index);
});
}
elements.stageNav.appendChild(stageTab);
});
lucide.createIcons();
}
function renderCurrentStage() {
const currentStage = state.stages[state.currentStage];
elements.stageNameDisplay.textContent = currentStage.name;
elements.stageRequiredIndicator.style.display = hasRequiredFields(currentStage) ? 'inline' : 'none';
elements.stagePredefinedBadge.style.display = currentStage.predefined ? 'inline' : 'none';
elements.formStage.innerHTML = '';
if (currentStage.fields.length === 0) {
elements.emptyState.style.display = 'block';
elements.formStage.appendChild(elements.emptyState);
} else {
elements.emptyState.style.display = 'none';
currentStage.fields.forEach((field, index) => {
const fieldElement = createFieldElement(field, index);
elements.formStage.appendChild(fieldElement);
});
}
lucide.createIcons();
}
function createFieldElement(field, index) {
const fieldDiv = document.createElement('div');
fieldDiv.className = `form-field bg-white border ${state.selectedField && state.selectedField.id === field.id ? 'border-2 border-temple-red ring-2 ring-temple-red/20' : 'border-gray-200 hover:border-temple-red'} rounded-xl p-4 mb-4 shadow-sm transition-all cursor-pointer relative`;
fieldDiv.dataset.fieldId = field.id;
fieldDiv.dataset.fieldIndex = index;
const fieldHeader = document.createElement('div');
fieldHeader.className = 'field-header flex justify-between items-center mb-3';
fieldHeader.innerHTML = `
<div class="field-title font-semibold text-gray-900 flex items-center gap-2">
<i data-lucide="${getFieldIcon(field.type)}" class="w-4 h-4 text-temple-red"></i>
${field.label || field.type.charAt(0).toUpperCase() + field.type.slice(1)}
${field.required ? '<span class="required-indicator text-red-500 font-bold"> *</span>' : ''}
</div>
<div class="field-actions flex gap-1">
<div class="action-btn edit-field w-8 h-8 rounded-full flex items-center justify-center bg-gray-50 border border-gray-200 hover:bg-temple-red hover:border-temple-red transition cursor-pointer" data-field-id="${field.id}">
<i data-lucide="edit-3" class="w-3 h-3"></i>
</div>
${!field.predefined ? `<div class="action-btn remove-field w-8 h-8 rounded-full flex items-center justify-center bg-gray-50 border border-gray-200 hover:bg-temple-red hover:border-temple-red transition cursor-pointer" data-field-index="${index}">
<i data-lucide="trash-2" class="w-3 h-3"></i>
</div>` : ''}
</div>
`;
const fieldContent = document.createElement('div');
fieldContent.className = 'field-content mt-3';
if (field.type === 'text' || field.type === 'email' || field.type === 'phone' || field.type === 'date') {
fieldContent.innerHTML = `
<label class="field-label block text-sm font-medium text-gray-700 mb-2">
${field.label || 'Field Label'}
${field.required ? '<span class="required-indicator text-red-500 font-bold"> *</span>' : ''}
</label>
<input type="text" class="field-input w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition bg-gray-50" placeholder="${field.placeholder || 'Enter value'}" disabled>
`;
} else if (field.type === 'textarea') {
fieldContent.innerHTML = `
<label class="field-label block text-sm font-medium text-gray-700 mb-2">
${field.label || 'Field Label'}
${field.required ? '<span class="required-indicator text-red-500 font-bold"> *</span>' : ''}
</label>
<textarea class="field-input w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition bg-gray-50 resize-none" rows="3" placeholder="${field.placeholder || 'Enter text'}" disabled></textarea>
`;
} else if (field.type === 'file') {
fieldContent.innerHTML = `
<label class="field-label block text-sm font-medium text-gray-700 mb-2">
${field.label || 'Field Label'}
${field.required ? '<span class="required-indicator text-red-500 font-bold"> *</span>' : ''}
</label>
<div class="file-upload-area border-2 border-dashed border-gray-300 rounded-xl p-8 text-center bg-gray-50 hover:border-temple-red hover:bg-temple-red/5 transition cursor-pointer">
<div class="file-upload-icon text-3xl text-temple-red mb-4">
<i data-lucide="cloud-upload" class="w-10 h-10"></i>
</div>
<div class="file-upload-text">
<p>Drag & drop your ${field.label.toLowerCase()} here or <strong class="text-temple-red">click to browse</strong></p>
</div>
<div class="file-upload-info text-xs text-gray-500 mt-3">
<p>Supported formats: ${field.fileTypes || '.pdf, .doc, .docx'} (Max ${field.maxFileSize || 5}MB)</p>
${field.multipleFiles ? `<p>Multiple files allowed (Max ${field.maxFiles || 1} files)</p>` : ''}
</div>
</div>
`;
} else if (field.type === 'select' || field.type === 'radio' || field.type === 'checkbox') {
fieldContent.innerHTML = `
<label class="field-label block text-sm font-medium text-gray-700 mb-2">
${field.label || 'Field Label'}
${field.required ? '<span class="required-indicator text-red-500 font-bold"> *</span>' : ''}
</label>
<div class="field-options space-y-2">
${field.options.map((option, idx) => `
<div class="option-item flex items-center gap-2">
<input type="${field.type}" id="${field.type}-${field.id}-${idx}" name="${field.type}-${field.id}" class="w-4 h-4 text-temple-red border-gray-300 rounded focus:ring-2 focus:ring-temple-red/20" disabled>
<label for="${field.type}-${field.id}-${idx}" class="text-sm text-gray-700 cursor-pointer">${option}</label>
</div>
`).join('')}
</div>
`;
}
fieldDiv.appendChild(fieldHeader);
fieldDiv.appendChild(fieldContent);
fieldDiv.addEventListener('click', (e) => {
if (!e.target.closest('.edit-field') && !e.target.closest('.remove-field')) {
selectField(field);
}
});
const editBtn = fieldDiv.querySelector('.edit-field');
if (editBtn) {
editBtn.addEventListener('click', (e) => {
e.stopPropagation();
selectField(field);
});
}
const removeBtn = fieldDiv.querySelector('.remove-field');
if (removeBtn) {
removeBtn.addEventListener('click', (e) => {
e.stopPropagation();
removeField(parseInt(removeBtn.dataset.fieldIndex));
});
}
fieldDiv.draggable = true;
fieldDiv.addEventListener('dragstart', (e) => {
state.draggedFieldIndex = parseInt(fieldDiv.dataset.fieldIndex);
e.dataTransfer.setData('text/plain', 'reorder');
e.dataTransfer.effectAllowed = 'move';
});
fieldDiv.addEventListener('dragover', (e) => e.preventDefault());
fieldDiv.addEventListener('drop', (e) => {
e.preventDefault();
const targetIndex = parseInt(fieldDiv.dataset.fieldIndex);
dropField(targetIndex);
});
return fieldDiv;
}
function showFieldEditor(field) {
elements.fieldEditor.style.display = 'flex';
elements.fieldLabel.value = field.label || '';
elements.fieldPlaceholder.value = field.placeholder || '';
elements.placeholderGroup.style.display = field.type !== 'file' ? 'block' : 'none';
elements.requiredField.checked = field.required || false;
const showOptions = field.type === 'select' || field.type === 'radio' || field.type === 'checkbox';
elements.optionsEditor.style.display = showOptions ? 'block' : 'none';
if (showOptions) {
renderOptionsEditor(field);
}
const showFileSettings = field.type === 'file';
elements.fileSettings.style.display = showFileSettings ? 'block' : 'none';
if (showFileSettings) {
elements.fileTypes.value = field.fileTypes || '.pdf,.doc,.docx';
elements.maxFileSize.value = field.maxFileSize || 5;
}
}
function renderOptionsEditor(field) {
elements.optionsList.innerHTML = '';
field.options.forEach((option, index) => {
const optionInput = document.createElement('div');
optionInput.className = 'option-input flex gap-2';
optionInput.innerHTML = `
<input type="text" class="flex-1 px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" value="${option}" placeholder="Option ${index + 1}">
<button class="remove-option w-8 h-8 rounded-full flex items-center justify-center bg-red-100 text-red-500 border-none cursor-pointer hover:bg-red-500 hover:text-white transition">
<i data-lucide="x" class="w-3 h-3"></i>
</button>
`;
elements.optionsList.appendChild(optionInput);
const input = optionInput.querySelector('input');
const removeBtn = optionInput.querySelector('.remove-option');
input.addEventListener('input', () => {
field.options[index] = input.value;
});
removeBtn.addEventListener('click', () => {
if (field.options.length > 1) {
field.options.splice(index, 1);
renderOptionsEditor(field);
}
});
});
lucide.createIcons();
}
// Event Handlers
function selectField(field) {
state.selectedField = field;
showFieldEditor(field);
document.querySelectorAll('.form-field').forEach(el => {
el.classList.remove('border-2', 'border-temple-red', 'ring-2', 'ring-temple-red/20');
el.classList.add('border-gray-200');
});
const selectedEl = document.querySelector(`[data-field-id="${field.id}"]`);
if (selectedEl) {
selectedEl.classList.remove('border-gray-200');
selectedEl.classList.add('border-2', 'border-temple-red', 'ring-2', 'ring-temple-red/20');
}
}
function clearSelection() {
state.selectedField = null;
elements.fieldEditor.style.display = 'none';
document.querySelectorAll('.form-field').forEach(el => {
el.classList.remove('border-2', 'border-temple-red', 'ring-2', 'ring-temple-red/20');
el.classList.add('border-gray-200');
});
}
function addStage() {
const newStage = {
id: state.nextStageId++,
name: `Custom Stage ${state.stages.filter(s => !s.predefined).length + 1}`,
predefined: false,
fields: []
};
state.stages.push(newStage);
state.currentStage = state.stages.length - 1;
renderStageNavigation();
renderCurrentStage();
}
function removeStage(index) {
const predefinedStages = state.stages.filter(s => s.predefined).length;
const isPredefined = state.stages[index].predefined;
if (isPredefined && predefinedStages <= 1 && state.stages.length === 1) {
return;
}
if (!isPredefined || state.stages.length > 1) {
state.stages.splice(index, 1);
if (state.currentStage >= state.stages.length) {
state.currentStage = state.stages.length - 1;
}
renderStageNavigation();
renderCurrentStage();
}
}
function removeField(index) {
const currentStage = state.stages[state.currentStage];
if (!currentStage.fields[index].predefined) {
currentStage.fields.splice(index, 1);
if (state.selectedField && state.selectedField.id === currentStage.fields[index]?.id) {
clearSelection();
}
renderCurrentStage();
}
}
function openRenameModal(stage) {
elements.stageName.value = stage.name;
elements.renameModal.classList.remove('opacity-0', 'invisible');
elements.renameModal.querySelector('.transform').classList.remove('scale-90');
elements.stageName.focus();
elements.renameModal.dataset.stageId = stage.id;
}
function closeRenameModal() {
elements.renameModal.classList.add('opacity-0', 'invisible');
elements.renameModal.querySelector('.transform').classList.add('scale-90');
}
function saveStageName() {
const stageId = parseInt(elements.renameModal.dataset.stageId);
const newName = elements.stageName.value.trim();
if (newName === '') {
alert('Stage name cannot be empty');
return;
}
const stage = state.stages.find(s => s.id === stageId);
if (stage) {
stage.name = newName;
renderStageNavigation();
renderCurrentStage();
}
closeRenameModal();
}
function openFormSettingsModal() {
elements.formName.value = state.formName;
elements.formDescription.value = state.formDescription;
elements.formActive.checked = state.formActive;
elements.formSettingsModal.classList.remove('opacity-0', 'invisible');
elements.formSettingsModal.querySelector('.transform').classList.remove('scale-90');
}
function closeFormSettingsModal() {
elements.formSettingsModal.classList.add('opacity-0', 'invisible');
elements.formSettingsModal.querySelector('.transform').classList.add('scale-90');
}
function saveFormSettings() {
const newName = elements.formName.value.trim();
if (newName === '') {
alert('Form name cannot be empty');
return;
}
state.formName = newName;
state.formDescription = elements.formDescription.value;
state.formActive = elements.formActive.checked;
elements.formTitle.textContent = state.formName;
closeFormSettingsModal();
}
function saveForm() {
saveFormTemplate();
}
// Drag and Drop Handlers
function startStageDrag(event, index) {
state.draggedStageIndex = index;
event.dataTransfer.setData('text/plain', 'stage-reorder');
event.dataTransfer.effectAllowed = 'move';
}
function allowStageDrop(event) {
event.preventDefault();
}
function dragStageEnter(event) {
event.target.classList.add('border-temple-red', 'bg-temple-red/5', 'border-2');
}
function dragStageLeave(event) {
event.target.classList.remove('border-temple-red', 'bg-temple-red/5', 'border-2');
}
function dropStage(event, targetIndex) {
event.preventDefault();
event.target.classList.remove('border-temple-red', 'bg-temple-red/5', 'border-2');
if (state.draggedStageIndex !== null && state.draggedStageIndex !== targetIndex) {
const draggedStage = state.stages[state.draggedStageIndex];
state.stages.splice(state.draggedStageIndex, 1);
const adjustedIndex = state.draggedStageIndex < targetIndex ? targetIndex - 1 : targetIndex;
state.stages.splice(adjustedIndex, 0, draggedStage);
if (state.currentStage === state.draggedStageIndex) {
state.currentStage = adjustedIndex;
} else if (state.currentStage > state.draggedStageIndex && state.currentStage <= adjustedIndex) {
state.currentStage--;
} else if (state.currentStage < state.draggedStageIndex && state.currentStage >= adjustedIndex) {
state.currentStage++;
}
renderStageNavigation();
renderCurrentStage();
}
state.draggedStageIndex = null;
}
function startDrag(event, type, label) {
state.draggedField = { type, label };
event.dataTransfer.setData('text/plain', 'field');
event.dataTransfer.effectAllowed = 'copy';
}
function allowDrop(event) {
event.preventDefault();
}
function dragEnter(event) {
event.target.classList.add('border-temple-red', 'bg-temple-red/5', 'border-2');
}
function dragLeave() {
document.querySelectorAll('.form-stage').forEach(el => {
el.classList.remove('border-temple-red', 'bg-temple-red/5', 'border-2');
});
}
function drop(event) {
event.preventDefault();
dragLeave();
if (state.draggedField) {
const newField = {
id: state.nextFieldId++,
type: state.draggedField.type,
label: state.draggedField.label,
placeholder: '',
required: false,
options: state.draggedField.type === 'select' || state.draggedField.type === 'radio' || state.draggedField.type === 'checkbox'
? ['Option 1', 'Option 2']
: [],
fileTypes: state.draggedField.type === 'file' ? '.pdf,.doc,.docx' : '',
maxFileSize: state.draggedField.type === 'file' ? 5 : 0,
multipleFiles: state.draggedField.type === 'file' ? false : undefined,
maxFiles: state.draggedField.type === 'file' ? 1 : undefined,
predefined: false,
uploadedFiles: state.draggedField.type === 'file' ? [] : undefined
};
state.stages[state.currentStage].fields.push(newField);
selectField(newField);
state.draggedField = null;
renderCurrentStage();
}
}
function dropField(targetIndex) {
if (state.draggedFieldIndex !== null && state.draggedFieldIndex !== targetIndex) {
const currentStage = state.stages[state.currentStage];
const draggedField = currentStage.fields[state.draggedFieldIndex];
currentStage.fields.splice(state.draggedFieldIndex, 1);
const adjustedIndex = state.draggedFieldIndex < targetIndex ? targetIndex - 1 : targetIndex;
currentStage.fields.splice(adjustedIndex, 0, draggedField);
renderCurrentStage();
}
state.draggedFieldIndex = null;
}
// Initialize Event Listeners
function initEventListeners() {
setTimeout(() => {
document.querySelectorAll('.field-item').forEach(item => {
if (!item.hasAttribute('data-drag-initialized')) {
item.addEventListener('dragstart', (e) => {
const type = item.dataset.type;
const label = item.dataset.label;
startDrag(e, type, label);
});
item.setAttribute('data-drag-initialized', 'true');
}
});
if (elements.formStage) {
elements.formStage.addEventListener('drop', drop);
elements.formStage.addEventListener('dragover', allowDrop);
elements.formStage.addEventListener('dragenter', dragEnter);
elements.formStage.addEventListener('dragleave', dragLeave);
elements.formStage.addEventListener('click', clearSelection);
}
if (elements.addStageBtn) elements.addStageBtn.addEventListener('click', addStage);
if (elements.saveFormBtn) elements.saveFormBtn.addEventListener('click', saveForm);
if (elements.renameStageBtn) {
elements.renameStageBtn.addEventListener('click', () => {
if (state.stages[state.currentStage]) {
openRenameModal(state.stages[state.currentStage]);
}
});
}
if (elements.formSettingsBtn) elements.formSettingsBtn.addEventListener('click', openFormSettingsModal);
if (elements.closeRenameModal) elements.closeRenameModal.addEventListener('click', closeRenameModal);
if (elements.cancelRenameBtn) elements.cancelRenameBtn.addEventListener('click', closeRenameModal);
if (elements.saveRenameBtn) elements.saveRenameBtn.addEventListener('click', saveStageName);
if (elements.renameModal) {
elements.renameModal.addEventListener('click', (e) => {
if (e.target === elements.renameModal) closeRenameModal();
});
}
if (elements.closeFormSettingsModal) elements.closeFormSettingsModal.addEventListener('click', closeFormSettingsModal);
if (elements.cancelFormSettingsBtn) elements.cancelFormSettingsBtn.addEventListener('click', closeFormSettingsModal);
if (elements.saveFormSettingsBtn) elements.saveFormSettingsBtn.addEventListener('click', saveFormSettings);
if (elements.formSettingsModal) {
elements.formSettingsModal.addEventListener('click', (e) => {
if (e.target === elements.formSettingsModal) closeFormSettingsModal();
});
}
if (elements.closeEditorBtn) elements.closeEditorBtn.addEventListener('click', clearSelection);
if (elements.fieldLabel) {
elements.fieldLabel.addEventListener('input', () => {
if (state.selectedField) {
state.selectedField.label = elements.fieldLabel.value;
renderCurrentStage();
}
});
}
if (elements.fieldPlaceholder) {
elements.fieldPlaceholder.addEventListener('input', () => {
if (state.selectedField) {
state.selectedField.placeholder = elements.fieldPlaceholder.value;
}
});
}
if (elements.requiredField) {
elements.requiredField.addEventListener('change', () => {
if (state.selectedField) {
state.selectedField.required = elements.requiredField.checked;
renderStageNavigation();
renderCurrentStage();
}
});
}
if (elements.addOptionBtn) {
elements.addOptionBtn.addEventListener('click', () => {
if (state.selectedField &&
(state.selectedField.type === 'select' ||
state.selectedField.type === 'radio' ||
state.selectedField.type === 'checkbox')) {
state.selectedField.options.push('New Option');
renderOptionsEditor(state.selectedField);
}
});
}
if (elements.fileTypes) {
elements.fileTypes.addEventListener('input', () => {
if (state.selectedField && state.selectedField.type === 'file') {
state.selectedField.fileTypes = elements.fileTypes.value;
}
});
}
if (elements.maxFileSize) {
elements.maxFileSize.addEventListener('input', () => {
if (state.selectedField && state.selectedField.type === 'file') {
state.selectedField.maxFileSize = parseInt(elements.maxFileSize.value) || 5;
}
});
}
if (elements.multipleFiles) {
elements.multipleFiles.addEventListener('change', function() {
elements.maxFiles.disabled = !this.checked;
if (!this.checked) {
elements.maxFiles.value = 1;
if (state.selectedField) {
state.selectedField.maxFiles = 1;
}
}
});
}
}, 100);
}
// Initialize Application
function init() {
if (elements.formTitle) {
elements.formTitle.textContent = 'Loading...';
}
if (elements.formStage) {
elements.formStage.style.display = 'none';
}
if (elements.emptyState) {
elements.emptyState.style.display = 'block';
elements.emptyState.innerHTML = '<i data-lucide="loader-2" class="w-12 h-12 mx-auto mb-4 text-temple-red animate-spin"></i><p>Loading form template...</p>';
}
initEventListeners();
lucide.createIcons();
if (djangoConfig.loadUrl) {
loadExistingTemplate();
} else {
if (elements.formTitle) {
elements.formTitle.textContent = 'New Form Template';
}
if (elements.formStage) {
elements.formStage.style.display = 'block';
}
if (elements.emptyState) {
elements.emptyState.style.display = 'block';
elements.emptyState.innerHTML = '<i data-lucide="cloud-upload" class="w-12 h-12 mx-auto mb-4 text-gray-300"></i><p>Drag form elements here to build your stage</p>';
}
renderStageNavigation();
renderCurrentStage();
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
</script>
</body>
</html>