1799 lines
71 KiB
HTML
1799 lines
71 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>ATS Form Builder - Vanilla JS</title>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<style>
|
|
/* Your existing CSS styles - exactly the same as before */
|
|
:root {
|
|
--primary: #4361ee;
|
|
--primary-light: #4895ef;
|
|
--secondary: #3f37c9;
|
|
--success: #4cc9f0;
|
|
--light: #f8f9fa;
|
|
--dark: #212529;
|
|
--gray: #6c757d;
|
|
--light-gray: #e9ecef;
|
|
--border: #dee2e6;
|
|
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
--radius: 8px;
|
|
--transition: all 0.3s ease;
|
|
}
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
}
|
|
body {
|
|
background-color: #f5f7fb;
|
|
color: var(--dark);
|
|
line-height: 1.6;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
}
|
|
.container {
|
|
display: flex;
|
|
height: 100vh;
|
|
}
|
|
/* Sidebar Styles */
|
|
.sidebar {
|
|
width: 280px;
|
|
background: white;
|
|
border-right: 1px solid var(--border);
|
|
padding: 20px;
|
|
overflow-y: auto;
|
|
box-shadow: var(--shadow);
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.sidebar-header {
|
|
padding-bottom: 20px;
|
|
margin-bottom: 20px;
|
|
border-bottom: 1px solid var(--border);
|
|
flex-shrink: 0;
|
|
}
|
|
.sidebar-header h2 {
|
|
font-size: 1.5rem;
|
|
color: var(--primary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.field-categories {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
}
|
|
.field-category {
|
|
margin-bottom: 25px;
|
|
}
|
|
.category-title {
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
margin-bottom: 12px;
|
|
color: var(--gray);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.field-item {
|
|
background: var(--light);
|
|
border: 1px dashed var(--border);
|
|
border-radius: var(--radius);
|
|
padding: 12px 15px;
|
|
margin-bottom: 10px;
|
|
cursor: grab;
|
|
transition: var(--transition);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.field-item:hover {
|
|
background: var(--light-gray);
|
|
transform: translateY(-2px);
|
|
border-color: var(--primary-light);
|
|
}
|
|
.field-item i {
|
|
color: var(--primary);
|
|
font-size: 1.2rem;
|
|
}
|
|
.field-item span {
|
|
font-weight: 500;
|
|
}
|
|
/* Main Content Styles */
|
|
.main-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 20px;
|
|
overflow: hidden;
|
|
}
|
|
.toolbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 15px;
|
|
padding-bottom: 15px;
|
|
border-bottom: 1px solid var(--border);
|
|
flex-shrink: 0;
|
|
}
|
|
.toolbar h1 {
|
|
font-size: 1.8rem;
|
|
color: var(--primary);
|
|
}
|
|
.btn {
|
|
padding: 10px 20px;
|
|
border-radius: var(--radius);
|
|
border: none;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.btn-primary {
|
|
background: var(--primary);
|
|
color: white;
|
|
}
|
|
.btn-primary:hover {
|
|
background: var(--secondary);
|
|
}
|
|
.btn-outline {
|
|
background: transparent;
|
|
border: 1px solid var(--primary);
|
|
color: var(--primary);
|
|
}
|
|
.btn-outline:hover {
|
|
background: var(--primary);
|
|
color: white;
|
|
}
|
|
/* Stage Navigation - Fixed Height with Scroll */
|
|
.stage-nav-container {
|
|
height: 60px;
|
|
overflow-x: scroll;
|
|
overflow-y: hidden;
|
|
padding: 5px;
|
|
margin-bottom: 15px;
|
|
flex-shrink: 0;
|
|
background: white;
|
|
border-radius: var(--radius);
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
}
|
|
.stage-nav {
|
|
display: flex;
|
|
min-width: 100%;
|
|
padding: 0 10px;
|
|
white-space: nowrap;
|
|
}
|
|
.stage-tab {
|
|
padding: 12px 20px;
|
|
background: var(--light);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius) var(--radius) 0 0;
|
|
margin-right: 5px;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
transition: var(--transition);
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-shrink: 0;
|
|
}
|
|
.stage-tab.active {
|
|
background: white;
|
|
border-bottom: 1px solid transparent;
|
|
z-index: 2;
|
|
}
|
|
.stage-tab:hover:not(.active) {
|
|
background: var(--light-gray);
|
|
}
|
|
.stage-tab .rename-btn {
|
|
opacity: 0;
|
|
transition: opacity 0.2s;
|
|
}
|
|
.stage-tab:hover .rename-btn {
|
|
opacity: 1;
|
|
}
|
|
.stage-tab .remove-btn {
|
|
position: absolute;
|
|
right: 8px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
opacity: 0;
|
|
transition: opacity 0.2s;
|
|
}
|
|
.stage-tab:hover .remove-btn {
|
|
opacity: 1;
|
|
}
|
|
/* Form Builder Area */
|
|
.form-builder-container {
|
|
display: flex;
|
|
gap: 20px;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
.form-builder {
|
|
background: white;
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow);
|
|
flex: 1;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.form-header {
|
|
padding: 20px;
|
|
border-bottom: 1px solid var(--border);
|
|
background: var(--light);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.form-header h3 {
|
|
font-size: 1.3rem;
|
|
color: var(--primary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.rename-stage-btn {
|
|
background: none;
|
|
border: none;
|
|
color: var(--gray);
|
|
cursor: pointer;
|
|
padding: 5px;
|
|
border-radius: 4px;
|
|
transition: var(--transition);
|
|
}
|
|
.rename-stage-btn:hover {
|
|
background: var(--light-gray);
|
|
color: var(--primary);
|
|
}
|
|
.form-stage {
|
|
padding: 20px;
|
|
min-height: 300px;
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
}
|
|
.form-field {
|
|
background: white;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: 15px;
|
|
margin-bottom: 15px;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
position: relative;
|
|
transition: var(--transition);
|
|
cursor: pointer;
|
|
}
|
|
.form-field:hover {
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
border-color: var(--primary-light);
|
|
}
|
|
.form-field.selected {
|
|
border: 2px solid var(--primary);
|
|
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
|
|
}
|
|
.field-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
.field-title {
|
|
font-weight: 600;
|
|
color: var(--dark);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.required-indicator {
|
|
color: #e53935;
|
|
font-weight: bold;
|
|
}
|
|
.field-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
.action-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--light);
|
|
border: 1px solid var(--border);
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
}
|
|
.action-btn:hover {
|
|
background: var(--primary);
|
|
color: white;
|
|
border-color: var(--primary);
|
|
}
|
|
.field-content {
|
|
margin-top: 10px;
|
|
}
|
|
.field-label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-weight: 500;
|
|
}
|
|
.field-input {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
font-size: 1rem;
|
|
}
|
|
.field-input:focus {
|
|
outline: none;
|
|
border-color: var(--primary);
|
|
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
|
|
}
|
|
.field-options {
|
|
margin-top: 10px;
|
|
}
|
|
.option-item {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 8px;
|
|
}
|
|
.option-item input[type="text"] {
|
|
flex: 1;
|
|
margin-left: 10px;
|
|
}
|
|
.add-option {
|
|
color: var(--primary);
|
|
cursor: pointer;
|
|
font-size: 0.9rem;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
margin-top: 5px;
|
|
}
|
|
.add-option:hover {
|
|
text-decoration: underline;
|
|
}
|
|
/* File Upload Specific Styles */
|
|
.file-upload-area {
|
|
border: 2px dashed var(--border);
|
|
border-radius: var(--radius);
|
|
padding: 30px 20px;
|
|
text-align: center;
|
|
background: var(--light);
|
|
transition: var(--transition);
|
|
cursor: pointer;
|
|
}
|
|
.file-upload-area:hover {
|
|
border-color: var(--primary);
|
|
background: rgba(67, 97, 238, 0.05);
|
|
}
|
|
.file-upload-icon {
|
|
font-size: 3rem;
|
|
color: var(--primary);
|
|
margin-bottom: 15px;
|
|
}
|
|
.file-upload-text {
|
|
margin-bottom: 15px;
|
|
}
|
|
.file-upload-text strong {
|
|
color: var(--primary);
|
|
}
|
|
.file-upload-info {
|
|
font-size: 0.9rem;
|
|
color: var(--gray);
|
|
margin-top: 10px;
|
|
}
|
|
.uploaded-file {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
background: white;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: 10px 15px;
|
|
margin-top: 15px;
|
|
}
|
|
.file-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.file-icon {
|
|
color: var(--primary);
|
|
}
|
|
.file-name {
|
|
font-weight: 500;
|
|
}
|
|
.file-size {
|
|
font-size: 0.85rem;
|
|
color: var(--gray);
|
|
}
|
|
.remove-file-btn {
|
|
background: none;
|
|
border: none;
|
|
color: #e53935;
|
|
cursor: pointer;
|
|
font-size: 1.2rem;
|
|
width: 30px;
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 50%;
|
|
transition: var(--transition);
|
|
}
|
|
.remove-file-btn:hover {
|
|
background: #ffebee;
|
|
}
|
|
/* Field Editor Panel */
|
|
.field-editor {
|
|
width: 320px;
|
|
background: white;
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow);
|
|
padding: 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
transition: var(--transition);
|
|
flex-shrink: 0;
|
|
}
|
|
.editor-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
padding-bottom: 15px;
|
|
border-bottom: 1px solid var(--border);
|
|
flex-shrink: 0;
|
|
}
|
|
.editor-header h3 {
|
|
font-size: 1.3rem;
|
|
color: var(--primary);
|
|
}
|
|
.editor-section {
|
|
margin-bottom: 20px;
|
|
}
|
|
.editor-section h4 {
|
|
font-size: 1.1rem;
|
|
margin-bottom: 12px;
|
|
color: var(--gray);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.form-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 6px;
|
|
font-weight: 500;
|
|
}
|
|
.form-control {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
font-size: 1rem;
|
|
}
|
|
.form-control:focus {
|
|
outline: none;
|
|
border-color: var(--primary);
|
|
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
|
|
}
|
|
.checkbox-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.checkbox-group input {
|
|
width: auto;
|
|
}
|
|
.options-list {
|
|
margin-top: 10px;
|
|
}
|
|
.option-input {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.option-input input {
|
|
flex: 1;
|
|
}
|
|
.remove-option {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: #ffebee;
|
|
color: #e53935;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
}
|
|
.remove-option:hover {
|
|
background: #e53935;
|
|
color: white;
|
|
}
|
|
/* Drag and Drop Styles */
|
|
.drag-over {
|
|
background-color: rgba(67, 97, 238, 0.1);
|
|
border: 2px dashed var(--primary);
|
|
}
|
|
.drag-ghost {
|
|
opacity: 0.5;
|
|
}
|
|
/* Empty State */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 40px 20px;
|
|
color: var(--gray);
|
|
}
|
|
.empty-state i {
|
|
font-size: 3rem;
|
|
margin-bottom: 15px;
|
|
color: var(--light-gray);
|
|
}
|
|
/* Stage Rename Modal */
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
.modal-overlay.active {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
}
|
|
.modal {
|
|
background: white;
|
|
border-radius: var(--radius);
|
|
padding: 25px;
|
|
width: 400px;
|
|
max-width: 90%;
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
|
transform: scale(0.9);
|
|
transition: transform 0.3s ease;
|
|
}
|
|
.modal-overlay.active .modal {
|
|
transform: scale(1);
|
|
}
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
.modal-header h3 {
|
|
font-size: 1.4rem;
|
|
color: var(--primary);
|
|
}
|
|
.close-modal {
|
|
background: none;
|
|
border: none;
|
|
font-size: 1.5rem;
|
|
cursor: pointer;
|
|
color: var(--gray);
|
|
transition: var(--transition);
|
|
}
|
|
.close-modal:hover {
|
|
color: var(--dark);
|
|
}
|
|
.modal-body {
|
|
margin-bottom: 20px;
|
|
}
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
}
|
|
/* Predefined Stage Indicator */
|
|
.predefined-badge {
|
|
background: var(--success);
|
|
color: white;
|
|
font-size: 0.7rem;
|
|
padding: 2px 6px;
|
|
border-radius: 10px;
|
|
margin-left: 5px;
|
|
}
|
|
/* Form Settings Modal */
|
|
.form-settings-modal .modal {
|
|
width: 500px;
|
|
max-width: 90%;
|
|
}
|
|
/* Responsive */
|
|
@media (max-width: 1100px) {
|
|
.field-editor {
|
|
display: none;
|
|
}
|
|
.form-builder-container {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
@media (max-width: 768px) {
|
|
.container {
|
|
flex-direction: column;
|
|
}
|
|
.sidebar {
|
|
width: 100%;
|
|
border-right: none;
|
|
border-bottom: 1px solid var(--border);
|
|
height: auto;
|
|
max-height: 40vh;
|
|
}
|
|
.stage-nav-container {
|
|
height: auto;
|
|
max-height: 120px;
|
|
}
|
|
.stage-nav {
|
|
flex-wrap: wrap;
|
|
}
|
|
.stage-tab {
|
|
margin-bottom: 5px;
|
|
}
|
|
}
|
|
.stage-tab.drag-over {
|
|
background-color: rgba(67, 97, 238, 0.1);
|
|
border: 2px dashed var(--primary);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Pass Django CSRF token and other data -->
|
|
<script>
|
|
// Django template variables - these will be processed by Django
|
|
const djangoConfig = {
|
|
csrfToken: "{{ csrf_token }}",
|
|
saveUrl: "{% url 'save_form_template' %}",
|
|
loadUrl: {% if template_id %}"{% url 'load_form_template' template_id %}"{% else %}null{% endif %},
|
|
templateId: {% if template_id %}{{ template_id }}{% else %}null{% endif %}
|
|
};
|
|
</script>
|
|
<div class="container">
|
|
<!-- Sidebar with form elements -->
|
|
<div class="sidebar">
|
|
<div class="sidebar-header">
|
|
<h2><i class="fas fa-cube"></i> Form Elements</h2>
|
|
</div>
|
|
<div class="field-categories">
|
|
<div class="field-category">
|
|
<div class="category-title">
|
|
<i class="fas fa-user"></i> Personal Information
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="text" data-label="First Name">
|
|
<i class="fas fa-font"></i>
|
|
<span>Text Input</span>
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="email" data-label="Email">
|
|
<i class="fas fa-envelope"></i>
|
|
<span>Email</span>
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="phone" data-label="Phone">
|
|
<i class="fas fa-phone"></i>
|
|
<span>Phone</span>
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="text" data-label="Address">
|
|
<i class="fas fa-map-marker-alt"></i>
|
|
<span>Address</span>
|
|
</div>
|
|
</div>
|
|
<div class="field-category">
|
|
<div class="category-title">
|
|
<i class="fas fa-file-alt"></i> Application Details
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="textarea" data-label="Cover Letter">
|
|
<i class="fas fa-align-left"></i>
|
|
<span>Text Area</span>
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="file" data-label="Resume">
|
|
<i class="fas fa-file-upload"></i>
|
|
<span>File Upload</span>
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="date" data-label="Available Date">
|
|
<i class="fas fa-calendar"></i>
|
|
<span>Date Picker</span>
|
|
</div>
|
|
</div>
|
|
<div class="field-category">
|
|
<div class="category-title">
|
|
<i class="fas fa-list"></i> Selection Fields
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="select" data-label="Position">
|
|
<i class="fas fa-caret-square-down"></i>
|
|
<span>Dropdown</span>
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="radio" data-label="Experience Level">
|
|
<i class="fas fa-dot-circle"></i>
|
|
<span>Radio Buttons</span>
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="checkbox" data-label="Skills">
|
|
<i class="fas fa-check-square"></i>
|
|
<span>Checkboxes</span>
|
|
</div>
|
|
</div>
|
|
<div class="field-category">
|
|
<div class="category-title">
|
|
<i class="fas fa-briefcase"></i> Professional Fields
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="text" data-label="Company">
|
|
<i class="fas fa-building"></i>
|
|
<span>Company</span>
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="text" data-label="Position">
|
|
<i class="fas fa-user-tie"></i>
|
|
<span>Position</span>
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="text" data-label="Degree">
|
|
<i class="fas fa-graduation-cap"></i>
|
|
<span>Degree</span>
|
|
</div>
|
|
<div class="field-item" draggable="true" data-type="text" data-label="Institution">
|
|
<i class="fas fa-school"></i>
|
|
<span>Institution</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Main Content Area -->
|
|
<div class="main-content">
|
|
<div class="toolbar">
|
|
<h1 id="formTitle">Resume Application Form</h1>
|
|
<div>
|
|
<button class="btn btn-outline" id="formSettingsBtn">
|
|
<i class="fas fa-cog"></i> Settings
|
|
</button>
|
|
<button class="btn btn-outline" id="addStageBtn">
|
|
<i class="fas fa-plus"></i> Add Stage
|
|
</button>
|
|
<button class="btn btn-primary" id="saveFormBtn">
|
|
<i class="fas fa-save"></i> Save Form
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<!-- Stage Navigation - Fixed Height with Scroll -->
|
|
<div class="stage-nav-container">
|
|
<div class="stage-nav" id="stageNav"></div>
|
|
</div>
|
|
<!-- Form Builder and Editor Container -->
|
|
<div class="form-builder-container">
|
|
<!-- Form Builder Area -->
|
|
<div class="form-builder">
|
|
<div class="form-header">
|
|
<h3 id="currentStageTitle">
|
|
<i class="fas fa-list"></i>
|
|
<span id="stageNameDisplay">Contact Information</span> Stage
|
|
<span class="required-indicator" id="stageRequiredIndicator" style="display: none;"> *</span>
|
|
<span class="predefined-badge" id="stagePredefinedBadge" style="display: none;">Default</span>
|
|
</h3>
|
|
<button class="rename-stage-btn" id="renameStageBtn" title="Rename stage">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
</div>
|
|
<div
|
|
class="form-stage"
|
|
id="formStage"
|
|
>
|
|
<div class="empty-state" id="emptyState">
|
|
<i class="fas fa-cloud-upload-alt"></i>
|
|
<p>Drag form elements here to build your stage</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Field Editor Panel -->
|
|
<div class="field-editor" id="fieldEditor" style="display: none;">
|
|
<div class="editor-header">
|
|
<h3><i class="fas fa-sliders-h"></i> Field Properties</h3>
|
|
<button class="btn btn-outline" id="closeEditorBtn">Close</button>
|
|
</div>
|
|
<div class="editor-section">
|
|
<div class="form-group">
|
|
<label for="fieldLabel">Field Label</label>
|
|
<input
|
|
type="text"
|
|
id="fieldLabel"
|
|
class="form-control"
|
|
placeholder="Enter field label"
|
|
>
|
|
</div>
|
|
<div class="form-group" id="placeholderGroup">
|
|
<label for="fieldPlaceholder">Placeholder</label>
|
|
<input
|
|
type="text"
|
|
id="fieldPlaceholder"
|
|
class="form-control"
|
|
placeholder="Enter placeholder text"
|
|
>
|
|
</div>
|
|
<div class="form-group">
|
|
<div class="checkbox-group">
|
|
<input
|
|
type="checkbox"
|
|
id="requiredField"
|
|
>
|
|
<label for="requiredField">Required Field</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Options Editor for Select, Radio, Checkbox -->
|
|
<div class="editor-section" id="optionsEditor" style="display: none;">
|
|
<h4><i class="fas fa-list"></i> Options</h4>
|
|
<div class="options-list" id="optionsList"></div>
|
|
<button class="add-option" id="addOptionBtn">
|
|
<i class="fas fa-plus"></i> Add Option
|
|
</button>
|
|
</div>
|
|
<!-- File Type Specific Settings -->
|
|
<div class="editor-section" id="fileSettings" style="display: none;">
|
|
<h4><i class="fas fa-file"></i> File Settings</h4>
|
|
<div class="form-group">
|
|
<label for="fileTypes">Allowed File Types</label>
|
|
<input
|
|
type="text"
|
|
id="fileTypes"
|
|
class="form-control"
|
|
placeholder=".pdf, .doc, .docx"
|
|
>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="maxFileSize">Max File Size (MB)</label>
|
|
<input
|
|
type="number"
|
|
id="maxFileSize"
|
|
class="form-control"
|
|
min="1"
|
|
max="100"
|
|
value="5"
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Stage Rename Modal -->
|
|
<div class="modal-overlay" id="renameModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>Rename Stage</h3>
|
|
<button class="close-modal" id="closeRenameModal">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="stageName">Stage Name</label>
|
|
<input
|
|
type="text"
|
|
id="stageName"
|
|
class="form-control"
|
|
placeholder="Enter stage name"
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-outline" id="cancelRenameBtn">Cancel</button>
|
|
<button class="btn btn-primary" id="saveRenameBtn">Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Form Settings Modal -->
|
|
<div class="modal-overlay form-settings-modal" id="formSettingsModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>Form Settings</h3>
|
|
<button class="close-modal" id="closeFormSettingsModal">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="formName" class="form-label">Form Name</label>
|
|
<input type="text" id="formName" class="form-control" placeholder="Enter form name">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="formDescription" class="form-label">Form Description</label>
|
|
<textarea id="formDescription" class="form-control" rows="3" placeholder="Enter form description"></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<div class="checkbox-group">
|
|
<input type="checkbox" id="formActive">
|
|
<label for="formActive">Active Form</label>
|
|
</div>
|
|
<small class="form-text text-muted">When active, this form will be available for applicants to fill out.</small>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-outline" id="cancelFormSettingsBtn">Cancel</button>
|
|
<button class="btn btn-primary" 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 the 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 the 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 the 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 the 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 // Initialize with Django template ID if editing
|
|
};
|
|
|
|
function allowStageDrop(event) {
|
|
event.preventDefault();
|
|
}
|
|
function dragStageEnter(event) {
|
|
event.target.classList.add('drag-over');
|
|
}
|
|
|
|
function dragStageLeave(event) {
|
|
event.target.classList.remove('drag-over');
|
|
}
|
|
|
|
function startStageDrag(event, index) {
|
|
state.draggedStageIndex = index;
|
|
event.dataTransfer.setData('text/plain', 'stage-reorder');
|
|
event.dataTransfer.effectAllowed = 'move';
|
|
}
|
|
function dropStage(event, targetIndex) {
|
|
event.preventDefault();
|
|
event.target.classList.remove('drag-over');
|
|
|
|
if (state.draggedStageIndex !== null && state.draggedStageIndex !== targetIndex) {
|
|
const draggedStage = state.stages[state.draggedStageIndex];
|
|
state.stages.splice(state.draggedStageIndex, 1);
|
|
|
|
// Adjust target index if we removed an element before it
|
|
const adjustedIndex = state.draggedStageIndex < targetIndex ? targetIndex - 1 : targetIndex;
|
|
state.stages.splice(adjustedIndex, 0, draggedStage);
|
|
|
|
// Update current stage if needed
|
|
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;
|
|
}
|
|
// 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'),
|
|
closeEditorBtn: document.getElementById('closeEditorBtn'),
|
|
// Form settings elements
|
|
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': 'fas fa-font',
|
|
'email': 'fas fa-envelope',
|
|
'phone': 'fas fa-phone',
|
|
'textarea': 'fas fa-align-left',
|
|
'file': 'fas fa-file-upload',
|
|
'date': 'fas fa-calendar',
|
|
'select': 'fas fa-caret-square-down',
|
|
'radio': 'fas fa-dot-circle',
|
|
'checkbox': 'fas fa-check-square'
|
|
};
|
|
return icons[type] || 'fas fa-question';
|
|
}
|
|
|
|
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_id: state.templateId, // Include template_id for updates
|
|
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
|
|
}))
|
|
}))
|
|
};
|
|
|
|
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) {
|
|
alert('Form template saved successfully! Template ID: ' + result.template_id);
|
|
// Update templateId for future saves (important for new templates)
|
|
state.templateId = result.template_id;
|
|
} 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 if editing
|
|
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;
|
|
// Set form settings
|
|
state.formName = templateData.name || 'Untitled Form';
|
|
state.formDescription = templateData.description || '';
|
|
state.formActive = templateData.is_active !== false; // Default to true if not set
|
|
|
|
// Update form title
|
|
elements.formTitle.textContent = state.formName;
|
|
elements.formName.value = state.formName;
|
|
elements.formDescription.value = state.formDescription;
|
|
elements.formActive.checked = state.formActive;
|
|
|
|
// Set stages
|
|
state.stages = templateData.stages;
|
|
state.templateId = templateData.id;
|
|
// Update next IDs to avoid conflicts
|
|
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;
|
|
renderStageNavigation();
|
|
renderCurrentStage();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading template:', error);
|
|
alert('Error loading template data.');
|
|
}
|
|
}
|
|
}
|
|
|
|
// DOM Rendering Functions (same as before)
|
|
function renderStageNavigation() {
|
|
elements.stageNav.innerHTML = '';
|
|
state.stages.forEach((stage, index) => {
|
|
const stageTab = document.createElement('div');
|
|
stageTab.className = `stage-tab ${index === state.currentStage ? 'active' : ''}`;
|
|
stageTab.dataset.index = index;
|
|
|
|
// Make stage tabs draggable
|
|
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"> *</span>' : ''}
|
|
${stage.predefined ? '<span class="predefined-badge">Default</span>' : ''}
|
|
<span class="rename-btn"><i class="fas fa-edit"></i></span>
|
|
${(!stage.predefined || state.stages.length > 1) ? '<span class="remove-btn"><i class="fas fa-times"></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);
|
|
});
|
|
}
|
|
|
|
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);
|
|
});
|
|
}
|
|
}
|
|
|
|
function createFieldElement(field, index) {
|
|
const fieldDiv = document.createElement('div');
|
|
fieldDiv.className = `form-field ${state.selectedField && state.selectedField.id === field.id ? 'selected' : ''}`;
|
|
fieldDiv.dataset.fieldId = field.id;
|
|
fieldDiv.dataset.fieldIndex = index;
|
|
const fieldHeader = document.createElement('div');
|
|
fieldHeader.className = 'field-header';
|
|
fieldHeader.innerHTML = `
|
|
<div class="field-title">
|
|
<i class="${getFieldIcon(field.type)}"></i>
|
|
${field.label || field.type.charAt(0).toUpperCase() + field.type.slice(1)}
|
|
${field.required ? '<span class="required-indicator"> *</span>' : ''}
|
|
</div>
|
|
<div class="field-actions">
|
|
<div class="action-btn edit-field" data-field-id="${field.id}">
|
|
<i class="fas fa-edit"></i>
|
|
</div>
|
|
${!field.predefined ? `<div class="action-btn remove-field" data-field-index="${index}">
|
|
<i class="fas fa-trash"></i>
|
|
</div>` : ''}
|
|
</div>
|
|
`;
|
|
const fieldContent = document.createElement('div');
|
|
fieldContent.className = 'field-content';
|
|
fieldContent.innerHTML = `
|
|
<label class="field-label">
|
|
${field.label || 'Field Label'}
|
|
${field.required ? '<span class="required-indicator"> *</span>' : ''}
|
|
</label>
|
|
`;
|
|
// Add field input based on type
|
|
if (field.type === 'text' || field.type === 'email' || field.type === 'phone' || field.type === 'date') {
|
|
const input = document.createElement('input');
|
|
input.type = 'text';
|
|
input.className = 'field-input';
|
|
input.placeholder = field.placeholder || 'Enter value';
|
|
input.disabled = true;
|
|
fieldContent.appendChild(input);
|
|
} else if (field.type === 'textarea') {
|
|
const textarea = document.createElement('textarea');
|
|
textarea.className = 'field-input';
|
|
textarea.rows = 3;
|
|
textarea.placeholder = field.placeholder || 'Enter text';
|
|
textarea.disabled = true;
|
|
fieldContent.appendChild(textarea);
|
|
} else if (field.type === 'file') {
|
|
const fileUpload = document.createElement('div');
|
|
fileUpload.className = 'file-upload-area';
|
|
fileUpload.innerHTML = `
|
|
<div class="file-upload-icon">
|
|
<i class="fas fa-cloud-upload-alt"></i>
|
|
</div>
|
|
<div class="file-upload-text">
|
|
<p>Drag & drop your resume here or <strong>click to browse</strong></p>
|
|
</div>
|
|
<div class="file-upload-info">
|
|
<p>Supported formats: ${field.fileTypes || '.pdf, .doc, .docx'} (Max ${field.maxFileSize || 5}MB)</p>
|
|
</div>
|
|
<input type="file" class="file-input" style="display: none;" accept="${field.fileTypes || '.pdf,.doc,.docx'}">
|
|
`;
|
|
if (field.uploadedFile) {
|
|
const uploadedFile = document.createElement('div');
|
|
uploadedFile.className = 'uploaded-file';
|
|
uploadedFile.innerHTML = `
|
|
<div class="file-info">
|
|
<i class="fas fa-file file-icon"></i>
|
|
<div>
|
|
<div class="file-name">${field.uploadedFile.name}</div>
|
|
<div class="file-size">${formatFileSize(field.uploadedFile.size)}</div>
|
|
</div>
|
|
</div>
|
|
<button class="remove-file-btn">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
`;
|
|
fileUpload.appendChild(uploadedFile);
|
|
}
|
|
fieldContent.appendChild(fileUpload);
|
|
} else if (field.type === 'select') {
|
|
const select = document.createElement('select');
|
|
select.className = 'field-input';
|
|
select.disabled = true;
|
|
field.options.forEach(option => {
|
|
const optionEl = document.createElement('option');
|
|
optionEl.textContent = option;
|
|
select.appendChild(optionEl);
|
|
});
|
|
fieldContent.appendChild(select);
|
|
} else if (field.type === 'radio' || field.type === 'checkbox') {
|
|
const optionsDiv = document.createElement('div');
|
|
optionsDiv.className = 'field-options';
|
|
field.options.forEach((option, idx) => {
|
|
const optionItem = document.createElement('div');
|
|
optionItem.className = 'option-item';
|
|
optionItem.innerHTML = `
|
|
<input type="${field.type === 'radio' ? 'radio' : 'checkbox'}"
|
|
id="${field.type}-${field.id}-${idx}"
|
|
name="${field.type}-${field.id}"
|
|
disabled>
|
|
<label for="${field.type}-${field.id}-${idx}">${option}</label>
|
|
`;
|
|
optionsDiv.appendChild(optionItem);
|
|
});
|
|
fieldContent.appendChild(optionsDiv);
|
|
}
|
|
fieldDiv.appendChild(fieldHeader);
|
|
fieldDiv.appendChild(fieldContent);
|
|
// Add event listeners
|
|
fieldDiv.addEventListener('click', (e) => {
|
|
if (!e.target.closest('.edit-field') && !e.target.closest('.remove-field') &&
|
|
!e.target.closest('.remove-file-btn')) {
|
|
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));
|
|
});
|
|
}
|
|
const removeFileBtn = fieldDiv.querySelector('.remove-file-btn');
|
|
if (removeFileBtn) {
|
|
removeFileBtn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const fieldId = parseInt(fieldDiv.dataset.fieldId);
|
|
const stage = state.stages[state.currentStage];
|
|
const field = stage.fields.find(f => f.id === fieldId);
|
|
if (field) {
|
|
field.uploadedFile = null;
|
|
renderCurrentStage();
|
|
}
|
|
});
|
|
}
|
|
// Make draggable
|
|
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;
|
|
// Show/hide options editor
|
|
const showOptions = field.type === 'select' || field.type === 'radio' || field.type === 'checkbox';
|
|
elements.optionsEditor.style.display = showOptions ? 'block' : 'none';
|
|
if (showOptions) {
|
|
renderOptionsEditor(field);
|
|
}
|
|
// Show/hide file settings
|
|
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';
|
|
optionInput.innerHTML = `
|
|
<input type="text" class="form-control" value="${option}" placeholder="Option ${index + 1}">
|
|
<button class="remove-option">
|
|
<i class="fas fa-times"></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);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Event Handlers (same as before, but updated saveForm function)
|
|
function selectField(field) {
|
|
state.selectedField = field;
|
|
showFieldEditor(field);
|
|
// Update UI to show selected field
|
|
document.querySelectorAll('.form-field').forEach(el => {
|
|
el.classList.remove('selected');
|
|
});
|
|
const selectedEl = document.querySelector(`[data-field-id="${field.id}"]`);
|
|
if (selectedEl) {
|
|
selectedEl.classList.add('selected');
|
|
}
|
|
}
|
|
|
|
function clearSelection() {
|
|
state.selectedField = null;
|
|
elements.fieldEditor.style.display = 'none';
|
|
document.querySelectorAll('.form-field').forEach(el => {
|
|
el.classList.remove('selected');
|
|
});
|
|
}
|
|
|
|
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.add('active');
|
|
elements.stageName.focus();
|
|
// Store reference to the stage being renamed
|
|
elements.renameModal.dataset.stageId = stage.id;
|
|
}
|
|
|
|
function closeRenameModal() {
|
|
elements.renameModal.classList.remove('active');
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
// Form Settings Modal Handlers
|
|
function openFormSettingsModal() {
|
|
elements.formName.value = state.formName;
|
|
elements.formDescription.value = state.formDescription;
|
|
elements.formActive.checked = state.formActive;
|
|
elements.formSettingsModal.classList.add('active');
|
|
}
|
|
|
|
function closeFormSettingsModal() {
|
|
elements.formSettingsModal.classList.remove('active');
|
|
}
|
|
|
|
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;
|
|
|
|
// Update the form title in the header
|
|
elements.formTitle.textContent = state.formName;
|
|
|
|
closeFormSettingsModal();
|
|
}
|
|
|
|
// Updated saveForm function to use Django API
|
|
function saveForm() {
|
|
saveFormTemplate();
|
|
}
|
|
|
|
// Drag and Drop Handlers (same as before)
|
|
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('drag-over');
|
|
}
|
|
|
|
function dragLeave(event) {
|
|
event.target.classList.remove('drag-over');
|
|
}
|
|
|
|
function drop(event) {
|
|
event.preventDefault();
|
|
event.target.classList.remove('drag-over');
|
|
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,
|
|
predefined: false,
|
|
uploadedFile: null
|
|
};
|
|
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() {
|
|
// Sidebar drag start
|
|
document.querySelectorAll('.field-item').forEach(item => {
|
|
item.addEventListener('dragstart', (e) => {
|
|
const type = item.dataset.type;
|
|
const label = item.dataset.label;
|
|
startDrag(e, type, label);
|
|
});
|
|
});
|
|
// Form stage drop zone
|
|
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);
|
|
// Button events
|
|
elements.addStageBtn.addEventListener('click', addStage);
|
|
elements.saveFormBtn.addEventListener('click', saveForm);
|
|
elements.renameStageBtn.addEventListener('click', () => {
|
|
openRenameModal(state.stages[state.currentStage]);
|
|
});
|
|
// Form settings button
|
|
elements.formSettingsBtn.addEventListener('click', openFormSettingsModal);
|
|
// Modal events
|
|
elements.closeRenameModal.addEventListener('click', closeRenameModal);
|
|
elements.cancelRenameBtn.addEventListener('click', closeRenameModal);
|
|
elements.saveRenameBtn.addEventListener('click', saveStageName);
|
|
elements.renameModal.addEventListener('click', (e) => {
|
|
if (e.target === elements.renameModal) {
|
|
closeRenameModal();
|
|
}
|
|
});
|
|
// Form settings modal events
|
|
elements.closeFormSettingsModal.addEventListener('click', closeFormSettingsModal);
|
|
elements.cancelFormSettingsBtn.addEventListener('click', closeFormSettingsModal);
|
|
elements.saveFormSettingsBtn.addEventListener('click', saveFormSettings);
|
|
elements.formSettingsModal.addEventListener('click', (e) => {
|
|
if (e.target === elements.formSettingsModal) {
|
|
closeFormSettingsModal();
|
|
}
|
|
});
|
|
// Field editor events
|
|
elements.closeEditorBtn.addEventListener('click', clearSelection);
|
|
elements.fieldLabel.addEventListener('input', () => {
|
|
if (state.selectedField) {
|
|
state.selectedField.label = elements.fieldLabel.value;
|
|
renderCurrentStage();
|
|
}
|
|
});
|
|
elements.fieldPlaceholder.addEventListener('input', () => {
|
|
if (state.selectedField) {
|
|
state.selectedField.placeholder = elements.fieldPlaceholder.value;
|
|
}
|
|
});
|
|
elements.requiredField.addEventListener('change', () => {
|
|
if (state.selectedField) {
|
|
state.selectedField.required = elements.requiredField.checked;
|
|
renderStageNavigation();
|
|
renderCurrentStage();
|
|
}
|
|
});
|
|
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);
|
|
}
|
|
});
|
|
elements.fileTypes.addEventListener('input', () => {
|
|
if (state.selectedField && state.selectedField.type === 'file') {
|
|
state.selectedField.fileTypes = elements.fileTypes.value;
|
|
}
|
|
});
|
|
elements.maxFileSize.addEventListener('input', () => {
|
|
if (state.selectedField && state.selectedField.type === 'file') {
|
|
state.selectedField.maxFileSize = parseInt(elements.maxFileSize.value) || 5;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initialize Application
|
|
function init() {
|
|
// Initialize form title
|
|
elements.formTitle.textContent = state.formName;
|
|
|
|
renderStageNavigation();
|
|
renderCurrentStage();
|
|
initEventListeners();
|
|
// Load existing template if editing
|
|
if (djangoConfig.loadUrl) {
|
|
loadExistingTemplate();
|
|
}
|
|
}
|
|
|
|
// Start the application
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
</script>
|
|
</body>
|
|
</html> |