716 lines
27 KiB
HTML
716 lines
27 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ form.title }} - Form Preview{% endblock %}
|
|
|
|
{% block content %}
|
|
{% if not is_embed %}
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1><i class="fas fa-wpforms"></i> Form Preview</h1>
|
|
<div>
|
|
<a href="{% url 'form_list' %}" class="btn btn-outline-secondary me-2">
|
|
<i class="fas fa-arrow-left"></i> Back to Forms
|
|
</a>
|
|
<a href="{% url 'form_embed' form.id %}" class="btn btn-outline-primary" target="_blank">
|
|
<i class="fas fa-code"></i> Get Embed Code
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Form Preview Container -->
|
|
<div class="{% if is_embed %}embed-container{% else %}container-fluid{% endif %}">
|
|
<div class="{% if is_embed %}embed-form-wrapper{% else %}row justify-content-center{% endif %}">
|
|
<div class="{% if is_embed %}embed-form{% else %}col-lg-8 col-md-10{% endif %}">
|
|
<!-- Form Header -->
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-primary text-white">
|
|
<h3 class="mb-1">{{ form.title }}</h3>
|
|
{% if form.description %}
|
|
<p class="mb-0 opacity-90">{{ form.description }}</p>
|
|
{% endif %}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<small class="text-muted">
|
|
<i class="fas fa-chart-bar"></i> {{ submission_count }} submissions
|
|
</small>
|
|
<small class="text-muted">
|
|
<i class="fas fa-calendar"></i> Created {{ form.created_at|date:"M d, Y" }}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Live Form Preview -->
|
|
<div class="card shadow-sm">
|
|
<div class="card-body p-0">
|
|
<div id="form-preview-container">
|
|
<!-- Form will be rendered here by Preact -->
|
|
<div class="text-center py-5">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading form...</span>
|
|
</div>
|
|
<p class="mt-3 text-muted">Loading form preview...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Analytics (only shown if not embedded) -->
|
|
{% if not is_embed %}
|
|
<div class="card shadow-sm mt-4">
|
|
<div class="card-header">
|
|
<h5><i class="fas fa-chart-line"></i> Form Analytics</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<h4 class="text-primary">{{ submission_count }}</h4>
|
|
<small class="text-muted">Total Submissions</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<h4 class="text-success">{{ form.created_at|timesince }}</h4>
|
|
<small class="text-muted">Time Created</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<h4 class="text-info">{{ form.structure.wizards|length|default:0 }}</h4>
|
|
<small class="text-muted">Form Steps</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<h4 class="text-warning">
|
|
{% if form.structure.wizards %}
|
|
{{ form.structure.wizards.0.fields|length|default:0 }}
|
|
{% else %}
|
|
0
|
|
{% endif %}
|
|
</h4>
|
|
<small class="text-muted">First Step Fields</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Modal -->
|
|
<div class="modal fade" id="successModal" tabindex="-1" aria-labelledby="successModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-success text-white">
|
|
<h5 class="modal-title" id="successModalLabel">
|
|
<i class="fas fa-check-circle"></i> Form Submitted Successfully!
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="mb-0">Thank you for submitting the form. Your response has been recorded.</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
<button type="button" class="btn btn-primary" onclick="resetForm()">Submit Another Response</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error Modal -->
|
|
<div class="modal fade" id="errorModal" tabindex="-1" aria-labelledby="errorModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-danger text-white">
|
|
<h5 class="modal-title" id="errorModalLabel">
|
|
<i class="fas fa-exclamation-triangle"></i> Submission Error
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="mb-0" id="errorMessage">An error occurred while submitting the form. Please try again.</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
{% if is_embed %}
|
|
<style>
|
|
.embed-container {
|
|
background: #f8f9fa;
|
|
padding: 0;
|
|
}
|
|
|
|
.embed-form-wrapper {
|
|
margin: 0;
|
|
}
|
|
|
|
.embed-form {
|
|
padding: 0;
|
|
}
|
|
|
|
.embed-form .card {
|
|
border: none;
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.embed-form .card-header {
|
|
border-radius: 0;
|
|
}
|
|
|
|
body {
|
|
background: #f8f9fa;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
</style>
|
|
{% endif %}
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script src="https://unpkg.com/preact@10.19.3/dist/preact.umd.js"></script>
|
|
<script src="https://unpkg.com/htm@3.1.1/dist/htm.umd.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
|
|
|
<!-- FilePond for file uploads -->
|
|
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
|
|
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
|
|
<script src="https://unpkg.com/filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.js"></script>
|
|
<script src="https://unpkg.com/filepond-plugin-file-validate-size/dist/filepond-plugin-file-validate-size.js"></script>
|
|
|
|
<script>
|
|
// Form data from Django
|
|
const formId = {{ form.id }};
|
|
const formStructure = {{ form.structure|safe }};
|
|
const isEmbed = {{ is_embed|yesno:"true,false" }};
|
|
|
|
const { h, Component, render, useState, useEffect, useRef } = preact;
|
|
const html = htm.bind(h);
|
|
|
|
// Field Components for Preview
|
|
class PreviewTextField extends Component {
|
|
render() {
|
|
const { field, value, onChange, error } = this.props;
|
|
return html`
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
${field.label}
|
|
${field.required ? html`<span class="text-danger">*</span>` : ''}
|
|
</label>
|
|
<input type="text"
|
|
class="form-control ${error ? 'is-invalid' : ''}"
|
|
placeholder=${field.placeholder || ''}
|
|
value=${value || ''}
|
|
onInput=${(e) => onChange(field.id, e.target.value)}
|
|
required=${field.required} />
|
|
${error ? html`<div class="invalid-feedback">${error}</div>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
class PreviewEmailField extends Component {
|
|
render() {
|
|
const { field, value, onChange, error } = this.props;
|
|
return html`
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
${field.label}
|
|
${field.required ? html`<span class="text-danger">*</span>` : ''}
|
|
</label>
|
|
<input type="email"
|
|
class="form-control ${error ? 'is-invalid' : ''}"
|
|
placeholder=${field.placeholder || ''}
|
|
value=${value || ''}
|
|
onInput=${(e) => onChange(field.id, e.target.value)}
|
|
required=${field.required} />
|
|
${error ? html`<div class="invalid-feedback">${error}</div>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
class PreviewPhoneField extends Component {
|
|
render() {
|
|
const { field, value, onChange, error } = this.props;
|
|
return html`
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
${field.label}
|
|
${field.required ? html`<span class="text-danger">*</span>` : ''}
|
|
</label>
|
|
<input type="tel"
|
|
class="form-control ${error ? 'is-invalid' : ''}"
|
|
placeholder=${field.placeholder || ''}
|
|
value=${value || ''}
|
|
onInput=${(e) => onChange(field.id, e.target.value)}
|
|
required=${field.required} />
|
|
${error ? html`<div class="invalid-feedback">${error}</div>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
class PreviewDateField extends Component {
|
|
render() {
|
|
const { field, value, onChange, error } = this.props;
|
|
return html`
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
${field.label}
|
|
${field.required ? html`<span class="text-danger">*</span>` : ''}
|
|
</label>
|
|
<input type="date"
|
|
class="form-control ${error ? 'is-invalid' : ''}"
|
|
value=${value || ''}
|
|
onInput=${(e) => onChange(field.id, e.target.value)}
|
|
required=${field.required} />
|
|
${error ? html`<div class="invalid-feedback">${error}</div>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
class PreviewFileField extends Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.filePondRef = null;
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.initFilePond();
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this.filePondRef) {
|
|
this.filePondRef.destroy();
|
|
}
|
|
}
|
|
|
|
initFilePond() {
|
|
const { field, onChange } = this.props;
|
|
const inputElement = document.getElementById(`file-upload-${field.id}`);
|
|
|
|
if (!inputElement || typeof FilePond === 'undefined') return;
|
|
|
|
this.filePondRef = FilePond.create(inputElement, {
|
|
allowMultiple: field.multiple || false,
|
|
maxFiles: field.maxFiles || 1,
|
|
maxFileSize: field.maxFileSize ? `${field.maxFileSize}MB` : '5MB',
|
|
acceptedFileTypes: field.fileTypes || ['*'],
|
|
labelIdle: 'Drag & Drop your files or <span class="filepond--label-action">Browse</span>',
|
|
credits: false,
|
|
onupdatefiles: (fileItems) => {
|
|
const files = fileItems.map(item => item.file);
|
|
onChange(field.id, files);
|
|
}
|
|
});
|
|
}
|
|
|
|
render() {
|
|
const { field, error } = this.props;
|
|
return html`
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
${field.label}
|
|
${field.required ? html`<span class="text-danger">*</span>` : ''}
|
|
</label>
|
|
<input id="file-upload-${field.id}" type="file" />
|
|
${error ? html`<div class="text-danger small mt-1">${error}</div>` : ''}
|
|
${field.fileTypes ? html`<div class="form-text">Accepted: ${field.fileTypes.join(', ')}</div>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
class PreviewDropdownField extends Component {
|
|
render() {
|
|
const { field, value, onChange, error } = this.props;
|
|
return html`
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
${field.label}
|
|
${field.required ? html`<span class="text-danger">*</span>` : ''}
|
|
</label>
|
|
<select class="form-select ${error ? 'is-invalid' : ''}"
|
|
value=${value || ''}
|
|
onChange=${(e) => onChange(field.id, e.target.value)}
|
|
required=${field.required}>
|
|
<option value="">Select an option...</option>
|
|
${field.options.map(option => html`
|
|
<option value=${option.value}>${option.value}</option>
|
|
`)}
|
|
</select>
|
|
${error ? html`<div class="invalid-feedback">${error}</div>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
class PreviewRadioField extends Component {
|
|
render() {
|
|
const { field, value, onChange, error } = this.props;
|
|
return html`
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
${field.label}
|
|
${field.required ? html`<span class="text-danger">*</span>` : ''}
|
|
</label>
|
|
${field.options.map(option => html`
|
|
<div class="form-check">
|
|
<input class="form-check-input"
|
|
type="radio"
|
|
name=${field.id}
|
|
id=${option.id}
|
|
value=${option.value}
|
|
checked=${value === option.value}
|
|
onChange=${(e) => onChange(field.id, e.target.value)}
|
|
required=${field.required} />
|
|
<label class="form-check-label" for=${option.id}>
|
|
${option.value}
|
|
</label>
|
|
</div>
|
|
`)}
|
|
${error ? html`<div class="text-danger small mt-1">${error}</div>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
class PreviewCheckboxField extends Component {
|
|
render() {
|
|
const { field, value = [], onChange, error } = this.props;
|
|
return html`
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
${field.label}
|
|
${field.required ? html`<span class="text-danger">*</span>` : ''}
|
|
</label>
|
|
${field.options.map(option => html`
|
|
<div class="form-check">
|
|
<input class="form-check-input"
|
|
type="checkbox"
|
|
id=${option.id}
|
|
value=${option.value}
|
|
checked=${value.includes(option.value)}
|
|
onChange=${(e) => {
|
|
const newValue = e.target.checked
|
|
? [...value, option.value]
|
|
: value.filter(v => v !== option.value);
|
|
onChange(field.id, newValue);
|
|
}} />
|
|
<label class="form-check-label" for=${option.id}>
|
|
${option.value}
|
|
</label>
|
|
</div>
|
|
`)}
|
|
${error ? html`<div class="text-danger small mt-1">${error}</div>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
class PreviewRatingField extends Component {
|
|
render() {
|
|
const { field, value, onChange, error } = this.props;
|
|
return html`
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
${field.label}
|
|
${field.required ? html`<span class="text-danger">*</span>` : ''}
|
|
</label>
|
|
<div class="rating-container">
|
|
${[1, 2, 3, 4, 5].map(n => html`
|
|
<span class="rating-star ${value >= n ? 'active' : ''}"
|
|
style="font-size: 24px; color: ${value >= n ? '#ffc107' : '#ddd'}; cursor: pointer; margin-right: 5px;"
|
|
onClick=${() => onChange(field.id, n)}>
|
|
★
|
|
</span>
|
|
`)}
|
|
</div>
|
|
${error ? html`<div class="text-danger small mt-1">${error}</div>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Field Factory
|
|
function createPreviewField(field, props) {
|
|
const fieldProps = { ...props, field };
|
|
|
|
switch (field.type) {
|
|
case 'text': return html`<${PreviewTextField} ...${fieldProps} />`;
|
|
case 'email': return html`<${PreviewEmailField} ...${fieldProps} />`;
|
|
case 'phone': return html`<${PreviewPhoneField} ...${fieldProps} />`;
|
|
case 'date': return html`<${PreviewDateField} ...${fieldProps} />`;
|
|
case 'file': return html`<${PreviewFileField} ...${fieldProps} />`;
|
|
case 'dropdown': return html`<${PreviewDropdownField} ...${fieldProps} />`;
|
|
case 'radio': return html`<${PreviewRadioField} ...${fieldProps} />`;
|
|
case 'checkbox': return html`<${PreviewCheckboxField} ...${fieldProps} />`;
|
|
case 'rating': return html`<${PreviewRatingField} ...${fieldProps} />`;
|
|
default: return html`<${PreviewTextField} ...${fieldProps} />`;
|
|
}
|
|
}
|
|
|
|
// Main Form Preview Component
|
|
class FormPreview extends Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
currentWizardIndex: 0,
|
|
formData: {},
|
|
errors: {},
|
|
isSubmitting: false,
|
|
isSubmitted: false
|
|
};
|
|
}
|
|
|
|
getCurrentWizard() {
|
|
return formStructure.wizards[this.state.currentWizardIndex] || null;
|
|
}
|
|
|
|
getCurrentWizardFields() {
|
|
const wizard = this.getCurrentWizard();
|
|
return wizard ? wizard.fields : [];
|
|
}
|
|
|
|
getTotalWizards() {
|
|
return formStructure.wizards ? formStructure.wizards.length : 0;
|
|
}
|
|
|
|
getProgress() {
|
|
return ((this.state.currentWizardIndex + 1) / this.getTotalWizards()) * 100;
|
|
}
|
|
|
|
validateCurrentWizard() {
|
|
const fields = this.getCurrentWizardFields();
|
|
const errors = {};
|
|
|
|
fields.forEach(field => {
|
|
const value = this.state.formData[field.id];
|
|
|
|
if (field.required && (!value || (Array.isArray(value) && value.length === 0))) {
|
|
errors[field.id] = `${field.label} is required`;
|
|
}
|
|
|
|
// Email validation
|
|
if (field.type === 'email' && value) {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(value)) {
|
|
errors[field.id] = 'Please enter a valid email address';
|
|
}
|
|
}
|
|
|
|
// Phone validation
|
|
if (field.type === 'phone' && value) {
|
|
const phoneRegex = /^[\d\s\-\+\(\)]+$/;
|
|
if (!phoneRegex.test(value)) {
|
|
errors[field.id] = 'Please enter a valid phone number';
|
|
}
|
|
}
|
|
});
|
|
|
|
this.setState({ errors });
|
|
return Object.keys(errors).length === 0;
|
|
}
|
|
|
|
handleFieldChange = (fieldId, value) => {
|
|
this.setState({
|
|
formData: { ...this.state.formData, [fieldId]: value },
|
|
errors: { ...this.state.errors, [fieldId]: null }
|
|
});
|
|
}
|
|
|
|
handleNext = () => {
|
|
if (this.validateCurrentWizard()) {
|
|
if (this.state.currentWizardIndex < this.getTotalWizards() - 1) {
|
|
this.setState({ currentWizardIndex: this.state.currentWizardIndex + 1 });
|
|
} else {
|
|
this.handleSubmit();
|
|
}
|
|
}
|
|
}
|
|
|
|
handlePrevious = () => {
|
|
if (this.state.currentWizardIndex > 0) {
|
|
this.setState({ currentWizardIndex: this.state.currentWizardIndex - 1 });
|
|
}
|
|
}
|
|
|
|
handleSubmit = async () => {
|
|
if (!this.validateCurrentWizard()) return;
|
|
|
|
this.setState({ isSubmitting: true });
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
|
|
// Add form data
|
|
Object.keys(this.state.formData).forEach(key => {
|
|
const value = this.state.formData[key];
|
|
if (Array.isArray(value)) {
|
|
value.forEach((file, index) => {
|
|
formData.append(`${key}_${index}`, file);
|
|
});
|
|
} else {
|
|
formData.append(key, value);
|
|
}
|
|
});
|
|
|
|
// Add CSRF token
|
|
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]');
|
|
if (csrfToken) {
|
|
formData.append('csrfmiddlewaretoken', csrfToken.value);
|
|
}
|
|
|
|
const response = await fetch(`/recruitment/forms/${formId}/submit/`, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
this.setState({ isSubmitted: true });
|
|
const modal = new bootstrap.Modal(document.getElementById('successModal'));
|
|
modal.show();
|
|
} else {
|
|
throw new Error(result.error || 'Submission failed');
|
|
}
|
|
} catch (error) {
|
|
console.error('Submission error:', error);
|
|
document.getElementById('errorMessage').textContent = error.message;
|
|
const modal = new bootstrap.Modal(document.getElementById('errorModal'));
|
|
modal.show();
|
|
} finally {
|
|
this.setState({ isSubmitting: false });
|
|
}
|
|
}
|
|
|
|
resetForm = () => {
|
|
this.setState({
|
|
currentWizardIndex: 0,
|
|
formData: {},
|
|
errors: {},
|
|
isSubmitting: false,
|
|
isSubmitted: false
|
|
});
|
|
}
|
|
|
|
render() {
|
|
const currentWizard = this.getCurrentWizard();
|
|
const currentFields = this.getCurrentWizardFields();
|
|
|
|
if (this.state.isSubmitted) {
|
|
return html`
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-check-circle text-success fa-4x mb-3"></i>
|
|
<h3>Thank You!</h3>
|
|
<p class="text-muted">Your form has been submitted successfully.</p>
|
|
<button class="btn btn-primary" onClick=${this.resetForm}>
|
|
Submit Another Response
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return html`
|
|
<div class="form-preview">
|
|
<!-- Progress Bar -->
|
|
${formStructure.settings && formStructure.settings.showProgress && this.getTotalWizards() > 1 ? html`
|
|
<div class="progress mb-4" style="height: 6px;">
|
|
<div class="progress-bar"
|
|
style="width: ${this.getProgress()}%; transition: width 0.3s ease;">
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
|
|
<!-- Wizard Title -->
|
|
${currentWizard ? html`
|
|
<h4 class="mb-4">${currentWizard.title || 'Step ' + (this.state.currentWizardIndex + 1)}</h4>
|
|
` : ''}
|
|
|
|
<!-- Form Fields -->
|
|
<form id="preview-form">
|
|
${currentFields.map(field =>
|
|
createPreviewField(field, {
|
|
key: field.id,
|
|
value: this.state.formData[field.id],
|
|
onChange: this.handleFieldChange,
|
|
error: this.state.errors[field.id]
|
|
})
|
|
)}
|
|
</form>
|
|
|
|
<!-- Navigation Buttons -->
|
|
<div class="d-flex justify-content-between mt-4">
|
|
<button type="button"
|
|
class="btn btn-outline-secondary"
|
|
onClick=${this.handlePrevious}
|
|
disabled=${this.state.currentWizardIndex === 0 || this.state.isSubmitting}>
|
|
<i class="fas fa-arrow-left"></i> Previous
|
|
</button>
|
|
|
|
<button type="button"
|
|
class="btn btn-primary"
|
|
onClick=${this.handleNext}
|
|
disabled=${this.state.isSubmitting}>
|
|
${this.state.isSubmitting ? html`
|
|
<span class="spinner-border spinner-border-sm me-2" role="status"></span>
|
|
Submitting...
|
|
` : ''}
|
|
${this.state.currentWizardIndex === this.getTotalWizards() - 1 ?
|
|
html`<i class="fas fa-check"></i> Submit` :
|
|
html`Next <i class="fas fa-arrow-right"></i>`
|
|
}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Initialize FilePond plugins
|
|
if (typeof FilePond !== 'undefined') {
|
|
FilePond.registerPlugin(
|
|
FilePondPluginFileValidateType,
|
|
FilePondPluginFileValidateSize
|
|
);
|
|
}
|
|
|
|
// Render the form preview
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const container = document.getElementById('form-preview-container');
|
|
if (container) {
|
|
render(html`<${FormPreview} />`, container);
|
|
}
|
|
});
|
|
|
|
// Global function for modal reset
|
|
window.resetForm = function() {
|
|
const container = document.getElementById('form-preview-container');
|
|
if (container) {
|
|
render(html`<${FormPreview} />`, container);
|
|
}
|
|
};
|
|
</script>
|
|
{% endblock %}
|