diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5bc9a32..07a99b8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -43,15 +43,21 @@ jobs:
# No per-check installs needed; everything is managed by uv
# ruff
- - name: Ruff format (check only)
+ - name: Formatting (check only)
if: matrix.check == 'ruff'
working-directory: efile_app
- run: uv run ruff format --check .
+ run: |
+ uv run ruff format --check .
+ uv run djlint --check .
+ git ls-files '*.css' | xargs -n 1 --verbose bash -c 'diff -ru "$0" <(uv run css-beautify "$0")'
+ git ls-files '*.js' | xargs -n 1 --verbose bash -c 'diff -ru "$0" <(uv run js-beautify "$0")'
- name: Ruff lint
if: matrix.check == 'ruff'
working-directory: efile_app
- run: uv run ruff check .
+ run: |
+ uv run ruff check .
+ uv run djlint .
# type checking
- name: Type check (ty)
diff --git a/README.md b/README.md
index 6f37cc5..c8c8519 100644
--- a/README.md
+++ b/README.md
@@ -173,11 +173,18 @@ Ruff is configured in `pyproject.toml` under `[tool.ruff]`.
- Lint the codebase:
```bash
uv run ruff check .
+ uv run djlint .
```
- - Auto-format (optional):
+ - Auto-format:
```bash
uv run ruff format .
```
+ - Auto-format HTML, JS, and CSS
+ ```bash
+ uv run djlint --reformat .
+ uv run css-beautify -r efile/static/css/*.css
+ uv run js-beautify -r efile/static/js/*.js
+ ```
Notes: Ruff targets Python 3.10, line length 120, and excludes Django migrations (`**/migrations/*`).
diff --git a/efile_app/efile/static/css/components/search-dropdown.css b/efile_app/efile/static/css/components/search-dropdown.css
index 600b6a8..559409d 100644
--- a/efile_app/efile/static/css/components/search-dropdown.css
+++ b/efile_app/efile/static/css/components/search-dropdown.css
@@ -1,174 +1,175 @@
/* Search Dropdown Component Styles */
.search-dropdown-container {
- position: relative;
+ position: relative;
}
.search-dropdown-input {
- width: 100%;
- border-radius: 0.375rem;
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ width: 100%;
+ border-radius: 0.375rem;
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.search-dropdown-input:focus {
- border-color: #86b7fe;
- outline: 0;
- box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+ border-color: #86b7fe;
+ outline: 0;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.search-dropdown-input:disabled {
- background-color: #e9ecef;
- opacity: 0.65;
- cursor: not-allowed;
+ background-color: #e9ecef;
+ opacity: 0.65;
+ cursor: not-allowed;
}
.search-dropdown-results {
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- background: #ffffff;
- border: 1px solid #ced4da;
- border-top: none;
- border-radius: 0 0 0.375rem 0.375rem;
- max-height: 200px;
- overflow-y: auto;
- z-index: 999;
- box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
- margin-top: 0;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ background: #ffffff;
+ border: 1px solid #ced4da;
+ border-top: none;
+ border-radius: 0 0 0.375rem 0.375rem;
+ max-height: 200px;
+ overflow-y: auto;
+ z-index: 999;
+ box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
+ margin-top: 0;
}
.search-dropdown-item {
- padding: 0.75rem;
- cursor: pointer;
- border-bottom: 1px solid #f8f9fa;
- transition: background-color 0.15s ease-in-out;
- background-color: #ffffff;
- color: #212529;
+ padding: 0.75rem;
+ cursor: pointer;
+ border-bottom: 1px solid #f8f9fa;
+ transition: background-color 0.15s ease-in-out;
+ background-color: #ffffff;
+ color: #212529;
}
.search-dropdown-item:hover,
.search-dropdown-item.highlighted {
- background-color: #e9ecef;
- color: #212529;
+ background-color: #e9ecef;
+ color: #212529;
}
.search-dropdown-item:last-child {
- border-bottom: none;
- border-radius: 0 0 0.375rem 0.375rem;
+ border-bottom: none;
+ border-radius: 0 0 0.375rem 0.375rem;
}
.search-dropdown-item strong {
- background-color: #fff3cd;
- padding: 0.125rem 0.25rem;
- border-radius: 0.25rem;
- font-weight: 600;
- color: #212529;
+ background-color: #fff3cd;
+ padding: 0.125rem 0.25rem;
+ border-radius: 0.25rem;
+ font-weight: 600;
+ color: #212529;
}
.search-no-results {
- padding: 0.75rem;
- color: #6c757d;
- font-style: italic;
- text-align: center;
- background-color: #ffffff;
+ padding: 0.75rem;
+ color: #6c757d;
+ font-style: italic;
+ text-align: center;
+ background-color: #ffffff;
}
.search-dropdown-selected {
- position: relative;
- top: 0;
- left: 0;
- right: 0;
- background: #ffffff;
- border: 1px solid #ced4da;
- border-radius: 0.375rem;
- padding: 0.375rem 2.5rem 0.375rem 0.75rem;
- display: none;
- align-items: center;
- justify-content: space-between;
- min-height: calc(1.5em + 0.75rem + 2px);
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
- margin-bottom: 0;
+ position: relative;
+ top: 0;
+ left: 0;
+ right: 0;
+ background: #ffffff;
+ border: 1px solid #ced4da;
+ border-radius: 0.375rem;
+ padding: 0.375rem 2.5rem 0.375rem 0.75rem;
+ display: none;
+ align-items: center;
+ justify-content: space-between;
+ min-height: calc(1.5em + 0.75rem + 2px);
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ margin-bottom: 0;
}
.search-dropdown-selected:focus-within {
- border-color: #86b7fe;
- box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+ border-color: #86b7fe;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.search-dropdown-selected .selected-text {
- flex: 1;
- color: #495057;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ flex: 1;
+ color: #495057;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.search-dropdown-selected .btn-clear {
- position: absolute;
- right: 0.75rem;
- top: 50%;
- transform: translateY(-50%);
- background: none;
- border: none;
- color: #6c757d;
- font-size: 1.25rem;
- cursor: pointer;
- padding: 0;
- line-height: 1;
- width: 1.5rem;
- height: 1.5rem;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: color 0.15s ease-in-out;
+ position: absolute;
+ right: 0.75rem;
+ top: 50%;
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ color: #6c757d;
+ font-size: 1.25rem;
+ cursor: pointer;
+ padding: 0;
+ line-height: 1;
+ width: 1.5rem;
+ height: 1.5rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.15s ease-in-out;
}
.search-dropdown-selected .btn-clear:hover {
- color: #495057;
+ color: #495057;
}
.search-dropdown-selected .btn-clear:focus {
- outline: 2px solid #86b7fe;
- outline-offset: 2px;
- border-radius: 0.25rem;
+ outline: 2px solid #86b7fe;
+ outline-offset: 2px;
+ border-radius: 0.25rem;
}
/* Responsive adjustments */
@media (max-width: 768px) {
- .search-dropdown-results {
- max-height: 200px;
- }
-
- .search-dropdown-item {
- padding: 0.5rem;
- }
-
- .search-no-results {
- padding: 0.5rem;
- }
+ .search-dropdown-results {
+ max-height: 200px;
+ }
+
+ .search-dropdown-item {
+ padding: 0.5rem;
+ }
+
+ .search-no-results {
+ padding: 0.5rem;
+ }
}
/* Accessibility improvements */
.search-dropdown-input[aria-expanded="true"] {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
}
/* Loading state */
.search-dropdown-container.loading .search-dropdown-input {
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23007bff' viewBox='0 0 16 16'%3E%3Cpath d='M8 3.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5H4a.5.5 0 0 1 0-1h3.5V4a.5.5 0 0 1 .5-.5z'/%3E%3Cpath d='M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm0-1A7 7 0 1 1 8 1a7 7 0 0 1 0 14z'/%3E%3C/svg%3E");
- background-repeat: no-repeat;
- background-position: right 0.75rem center;
- background-size: 1rem;
- animation: spin 1s linear infinite;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23007bff' viewBox='0 0 16 16'%3E%3Cpath d='M8 3.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5H4a.5.5 0 0 1 0-1h3.5V4a.5.5 0 0 1 .5-.5z'/%3E%3Cpath d='M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm0-1A7 7 0 1 1 8 1a7 7 0 0 1 0 14z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.75rem center;
+ background-size: 1rem;
+ animation: spin 1s linear infinite;
}
@keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
-}
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/efile_app/efile/static/css/confirmation.css b/efile_app/efile/static/css/confirmation.css
index 8b3546f..4cb170b 100644
--- a/efile_app/efile/static/css/confirmation.css
+++ b/efile_app/efile/static/css/confirmation.css
@@ -4,26 +4,35 @@
padding: 2rem;
text-align: center;
}
+
.success-icon {
color: #28a745;
font-size: 4rem;
margin-bottom: 1rem;
}
+
.success-message {
color: #155724;
font-size: 1.25rem;
margin-bottom: 2rem;
}
+
.btn-primary.custom-confirm {
background: #2c5aa0;
border-color: #2c5aa0;
}
+
.btn-primary.custom-confirm:hover {
background: #1e3d6f;
border-color: #1e3d6f;
}
@media (max-width: 576px) {
- .confirmation-container { padding: 1rem; }
- .success-icon { font-size: 3rem; }
-}
+ .confirmation-container {
+ padding: 1rem;
+ }
+
+ .success-icon {
+ font-size: 3rem;
+ }
+}
\ No newline at end of file
diff --git a/efile_app/efile/static/css/expert_form.css b/efile_app/efile/static/css/expert_form.css
index 4776241..2f422d5 100644
--- a/efile_app/efile/static/css/expert_form.css
+++ b/efile_app/efile/static/css/expert_form.css
@@ -1,188 +1,203 @@
body {
- background-color: #f8f9fa;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ background-color: #f8f9fa;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.form-container {
- background: white;
- padding: 2rem;
- margin: 2rem auto;
- max-width: 800px;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ background: white;
+ padding: 2rem;
+ margin: 2rem auto;
+ max-width: 800px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.section-header {
- color: #007bff;
- font-size: 1.25rem;
- font-weight: 600;
- margin-bottom: 0.5rem;
+ color: #007bff;
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin-bottom: 0.5rem;
- font-size: 2rem;
+ font-size: 2rem;
}
.section-description {
- color: #6c757d;
- font-size: 0.95rem;
- margin-bottom: 1.5rem;
+ color: #6c757d;
+ font-size: 0.95rem;
+ margin-bottom: 1.5rem;
}
.optional {
- color: #6c757d;
- font-size: 0.85rem;
- font-style: italic;
- margin-left: 0.5rem;
+ color: #6c757d;
+ font-size: 0.85rem;
+ font-style: italic;
+ margin-left: 0.5rem;
}
.form-label {
- font-weight: 500;
- color: #495057;
- margin-bottom: 0;
+ font-weight: 500;
+ color: #495057;
+ margin-bottom: 0;
}
.form-select,
.form-control {
- border: 1px solid #ced4da;
- border-radius: 4px;
- padding: 0.5rem 0.75rem;
+ border: 1px solid #ced4da;
+ border-radius: 4px;
+ padding: 0.5rem 0.75rem;
}
.form-select:focus,
.form-control:focus {
- border-color: #007bff;
- box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+ border-color: #007bff;
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.btn-outline-secondary {
- color: #6c757d;
- border-color: #6c757d;
+ color: #6c757d;
+ border-color: #6c757d;
}
.btn-outline-secondary:hover {
- background-color: #6c757d;
- border-color: #6c757d;
+ background-color: #6c757d;
+ border-color: #6c757d;
}
.btn-primary {
- background-color: #007bff;
- border-color: #007bff;
+ background-color: #007bff;
+ border-color: #007bff;
}
.btn-primary {
- --bs-btn-disabled-color: #ffffff;
- --bs-btn-disabled-bg: #333333;
- --bs-btn-disabled-border-color: #666666;
- --bs-btn-disabled-opacity:
+ --bs-btn-disabled-color: #ffffff;
+ --bs-btn-disabled-bg: #333333;
+ --bs-btn-disabled-border-color: #666666;
+ --bs-btn-disabled-opacity:
}
.checkbox-group {
- background-color: #f8f9fa;
- border: 1px solid #e9ecef;
- border-radius: 4px;
- padding: 1rem;
- margin-top: 1rem;
+ background-color: #f8f9fa;
+ border: 1px solid #e9ecef;
+ border-radius: 4px;
+ padding: 1rem;
+ margin-top: 1rem;
}
.form-check {
- margin-bottom: 0.5rem;
+ margin-bottom: 0.5rem;
}
.form-check-input {
- margin-top: 0.25rem;
+ margin-top: 0.25rem;
}
.form-check-label {
- font-size: 0.95rem;
- color: #495057;
+ font-size: 0.95rem;
+ color: #495057;
}
.party-section {
- background-color: #f8f9fa;
- border: 1px solid #e9ecef;
- border-radius: 4px;
- padding: 1.5rem;
- margin: 1rem 0;
+ background-color: #f8f9fa;
+ border: 1px solid #e9ecef;
+ border-radius: 4px;
+ padding: 1.5rem;
+ margin: 1rem 0;
}
.party-title {
- font-weight: 600;
- color: #495057;
- margin-bottom: 1rem;
+ font-weight: 600;
+ color: #495057;
+ margin-bottom: 1rem;
}
.loading-spinner {
- color: #007bff;
- font-size: 0.9rem;
- margin-top: 0.5rem;
+ color: #007bff;
+ font-size: 0.9rem;
+ margin-top: 0.5rem;
}
.dropdown-field:disabled {
- background-color: #f8f9fa;
- opacity: 0.65;
+ background-color: #f8f9fa;
+ opacity: 0.65;
}
.recommendation-notice {
- font-size: 0.875rem !important;
- padding: 8px 12px !important;
- margin: 1rem 0 2rem 0 !important;
- border: 1px solid #28a745;
- background-color: #d4edda;
- color: #155724;
- border-radius: 4px;
- animation: fadeInOut 4s ease-in-out;
+ font-size: 0.875rem !important;
+ padding: 8px 12px !important;
+ margin: 1rem 0 2rem 0 !important;
+ border: 1px solid #28a745;
+ background-color: #d4edda;
+ color: #155724;
+ border-radius: 4px;
+ animation: fadeInOut 4s ease-in-out;
}
.recommendation-notice i {
- color: #ffd700;
- margin-right: 6px;
+ color: #ffd700;
+ margin-right: 6px;
}
@keyframes fadeInOut {
- 0% { opacity: 0; transform: translateY(-10px); }
- 15% { opacity: 1; transform: translateY(0); }
- 85% { opacity: 1; transform: translateY(0); }
- 100% { opacity: 0; transform: translateY(-10px); }
+ 0% {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+
+ 15% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+
+ 85% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
}
option.recommended {
- font-weight: bold;
- background-color: #fff3cd;
+ font-weight: bold;
+ background-color: #fff3cd;
}
.court-container {
- position: relative;
+ position: relative;
}
/* Custom styles for better spacing and required field styling */
.required {
- color: #dc3545 !important;
- font-weight: bold;
+ color: #dc3545 !important;
+ font-weight: bold;
}
.subsection-header {
- margin-top: 3.5rem !important;
- margin-bottom: 1rem !important;
- color: #007bff;
- font-weight: 600;
+ margin-top: 3.5rem !important;
+ margin-bottom: 1rem !important;
+ color: #007bff;
+ font-weight: 600;
}
.subsection-header:first-of-type {
- margin-top: 2rem !important;
+ margin-top: 2rem !important;
}
/* Increase spacing between form rows */
.row.mb-3 {
- margin-bottom: 2.5rem !important;
+ margin-bottom: 2.5rem !important;
}
/* Add more space before the button section */
.d-flex.justify-content-between.mt-4 {
- margin-top: 4rem !important;
- padding-top: 1rem;
- border-top: 1px solid #e9ecef;
+ margin-top: 4rem !important;
+ padding-top: 1rem;
+ border-top: 1px solid #e9ecef;
}
h4 {
- margin-top: 2rem;
- margin-bottom: 1rem !important;
- color: #007bff;
+ margin-top: 2rem;
+ margin-bottom: 1rem !important;
+ color: #007bff;
}
\ No newline at end of file
diff --git a/efile_app/efile/static/css/login.css b/efile_app/efile/static/css/login.css
index 2124d48..953019b 100644
--- a/efile_app/efile/static/css/login.css
+++ b/efile_app/efile/static/css/login.css
@@ -1,165 +1,164 @@
body {
- background-color: #f8f9fa;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ background-color: #f8f9fa;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.main-container {
- max-width: 500px;
- margin: 2rem auto;
- padding: 0 1rem;
+ max-width: 500px;
+ margin: 2rem auto;
+ padding: 0 1rem;
}
.header-nav {
- background-color: white;
- border-radius: 25px;
- padding: 1rem 1.5rem;
- margin-bottom: 1rem;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ background-color: white;
+ border-radius: 25px;
+ padding: 1rem 1.5rem;
+ margin-bottom: 1rem;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.login-card {
- background-color: white;
- border-radius: 25px;
- padding: 2rem;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+ background-color: white;
+ border-radius: 25px;
+ padding: 2rem;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.brand-header {
- background: linear-gradient(135deg, #1e3a5f 0%, #2c5aa0 100%);
- color: white;
- padding: 1.5rem;
- border-radius: 20px 20px 0 0;
- margin: -2rem -2rem 2rem -2rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
+ background: linear-gradient(135deg, #1e3a5f 0%, #2c5aa0 100%);
+ color: white;
+ padding: 1.5rem;
+ border-radius: 20px 20px 0 0;
+ margin: -2rem -2rem 2rem -2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
}
.brand-title {
- font-size: 1.8rem;
- font-weight: 600;
- margin: 0;
+ font-size: 1.8rem;
+ font-weight: 600;
+ margin: 0;
}
.scales-icon {
- font-size: 1.5rem;
- margin-right: 0.5rem;
+ font-size: 1.5rem;
+ margin-right: 0.5rem;
}
.user-icon {
- background-color: white;
- color: #1e3a8a;
- width: 40px;
- height: 40px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 1.2rem;
+ background-color: white;
+ color: #1e3a8a;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.2rem;
}
.form-label {
- font-weight: 600;
- color: #374151;
- margin-bottom: 0.5rem;
+ font-weight: 600;
+ color: #374151;
+ margin-bottom: 0.5rem;
}
.form-control {
- border: 2px solid #d1d5db;
- border-radius: 10px;
- padding: 0.75rem 1rem;
- font-size: 1rem;
- transition: border-color 0.3s ease;
+ border: 2px solid #d1d5db;
+ border-radius: 10px;
+ padding: 0.75rem 1rem;
+ font-size: 1rem;
+ transition: border-color 0.3s ease;
}
.form-control:focus {
- border-color: #3b82f6;
- box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25);
+ border-color: #3b82f6;
+ box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25);
}
.btn-sign-in,
.btn-register,
-.btn-password-reset
- {
- background: #1e3a5f;
- border: none;
- border-radius: 25px;
- padding: 0.75rem 2rem;
- font-weight: 600;
- font-size: 1.1rem;
- color: white;
- transition: all 0.3s ease;
- width: 200px;
+.btn-password-reset {
+ background: #1e3a5f;
+ border: none;
+ border-radius: 25px;
+ padding: 0.75rem 2rem;
+ font-weight: 600;
+ font-size: 1.1rem;
+ color: white;
+ transition: all 0.3s ease;
+ width: 200px;
}
.btn-sign-in:hover,
.btn-register:hover,
.btn-password-reset:hover {
- background: #1e3a5f;
- filter: brightness(1.5);
- transform: translateY(-1px);
+ background: #1e3a5f;
+ filter: brightness(1.5);
+ transform: translateY(-1px);
}
.show-password,
.forgot-password {
- color: #6b7280;
- text-decoration: none;
- font-size: 0.9rem;
+ color: #6b7280;
+ text-decoration: none;
+ font-size: 0.9rem;
}
.show-password:hover,
.forgot-password:hover {
- color: #3b82f6;
- text-decoration: underline;
+ color: #3b82f6;
+ text-decoration: underline;
}
.divider {
- height: 2px;
- background: linear-gradient(90deg, transparent, #e5e7eb, transparent);
- margin: 2rem 0;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, #e5e7eb, transparent);
+ margin: 2rem 0;
}
.first-time-section h3 {
- font-size: 2rem;
- font-weight: 700;
- color: #374151;
- text-align: center;
- margin-bottom: 1.5rem;
+ font-size: 2rem;
+ font-weight: 700;
+ color: #374151;
+ text-align: center;
+ margin-bottom: 1.5rem;
}
.first-time-text {
- color: #6b7280;
- text-align: center;
- font-size: 1.1rem;
- line-height: 1.6;
- margin-bottom: 2rem;
+ color: #6b7280;
+ text-align: center;
+ font-size: 1.1rem;
+ line-height: 1.6;
+ margin-bottom: 2rem;
}
.home-icon {
- color: #6b7280;
- margin-right: 0.5rem;
+ color: #6b7280;
+ margin-right: 0.5rem;
}
.nav-text {
- color: #6b7280;
- font-weight: 500;
+ color: #6b7280;
+ font-weight: 500;
}
.alert {
- border-radius: 10px;
- margin-bottom: 1rem;
+ border-radius: 10px;
+ margin-bottom: 1rem;
}
.register-form {
- display: none;
+ display: none;
}
.register-form.show {
- display: block;
+ display: block;
}
.login-form.hide {
- display: none;
+ display: none;
}
.alert-error {
diff --git a/efile_app/efile/static/css/options.css b/efile_app/efile/static/css/options.css
index 6409f2e..7f16e19 100644
--- a/efile_app/efile/static/css/options.css
+++ b/efile_app/efile/static/css/options.css
@@ -1,77 +1,77 @@
body {
- background-color: #f8f9fa;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ background-color: #f8f9fa;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.section-title {
- color: #333;
- font-size: 2rem;
- font-weight: 600;
- margin-bottom: 2rem;
+ color: #333;
+ font-size: 2rem;
+ font-weight: 600;
+ margin-bottom: 2rem;
}
.option-card {
- background: white;
- border: 1px solid #e0e0e0;
- border-radius: 12px;
- padding: 2rem;
- margin-bottom: 1.5rem;
- transition: all 0.3s ease;
+ background: white;
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ padding: 2rem;
+ margin-bottom: 1.5rem;
+ transition: all 0.3s ease;
}
.option-card:hover {
- border-color: #2c5aa0;
- box-shadow: 0 4px 12px rgba(44, 90, 160, 0.15);
- transform: translateY(-2px);
+ border-color: #2c5aa0;
+ box-shadow: 0 4px 12px rgba(44, 90, 160, 0.15);
+ transform: translateY(-2px);
}
.button-group {
- display: flex;
- gap: 1.5rem;
- margin-top: 1rem;
+ display: flex;
+ gap: 1.5rem;
+ margin-top: 1rem;
}
.button-group .btn {
- flex: 1;
- max-width: 150px;
- padding: 0.75rem 1.5rem;
- border-radius: 25px;
- font-weight: 500;
- transition: all 0.2s ease;
+ flex: 1;
+ max-width: 150px;
+ padding: 0.75rem 1.5rem;
+ border-radius: 25px;
+ font-weight: 500;
+ transition: all 0.2s ease;
}
.button-group .btn:hover {
- transform: translateY(-1px);
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.button-group .btn i {
- margin-right: 0.5rem;
+ margin-right: 0.5rem;
}
.option-icon {
- width: 60px;
- height: 60px;
- color: #2c5aa0;
- border-radius: 8px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 2rem;
- margin-right: 1.5rem;
- flex-shrink: 0;
+ width: 60px;
+ height: 60px;
+ color: #2c5aa0;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2rem;
+ margin-right: 1.5rem;
+ flex-shrink: 0;
}
.option-content h3 {
- color: #333;
- font-size: 1.5rem;
- font-weight: 600;
- margin-bottom: 0.5rem;
+ color: #333;
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 0.5rem;
}
.option-content p {
- color: #666;
- font-size: 1.1rem;
- margin: 0;
- line-height: 1.4;
-}
+ color: #666;
+ font-size: 1.1rem;
+ margin: 0;
+ line-height: 1.4;
+}
\ No newline at end of file
diff --git a/efile_app/efile/static/css/register.css b/efile_app/efile/static/css/register.css
index 541ce4e..818f4d1 100644
--- a/efile_app/efile/static/css/register.css
+++ b/efile_app/efile/static/css/register.css
@@ -1,10 +1,12 @@
.form-select#state option {
font-size: 1.15rem;
}
+
body {
background-color: #f8f9fa;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
+
.alert-warning {
background-color: #fef3c7;
border: 1px solid #f59e0b;
@@ -12,17 +14,20 @@ body {
padding: 1rem;
margin-bottom: 1.5rem;
}
+
.alert-warning p {
color: #92400e;
margin: 0;
font-size: 0.9rem;
line-height: 1.4;
}
+
.alert-warning strong {
color: #92400e;
display: block;
margin-bottom: 0.5rem;
}
+
.brand-header {
background: linear-gradient(135deg, #1e3a5f 0%, #2c5aa0 100%);
color: white;
@@ -30,11 +35,13 @@ body {
display: flex;
align-items: center;
}
+
.brand-title {
font-size: 1.8rem;
font-weight: 600;
margin: 0;
}
+
.btn-back {
background: linear-gradient(135deg, #6b7280 0%, #9ca3af 100%);
border: none;
@@ -46,11 +53,13 @@ body {
transition: all 0.3s ease;
min-width: 150px;
}
+
.btn-back:hover {
background: linear-gradient(135deg, #4b5563 0%, #6b7280 100%);
transform: translateY(-1px);
color: white;
}
+
.btn-container {
display: flex;
gap: 1rem;
@@ -58,6 +67,7 @@ body {
margin-top: 2rem;
flex-wrap: wrap;
}
+
.btn-register {
background: #1e3a5f;
border: none;
@@ -69,30 +79,37 @@ body {
transition: all 0.3s ease;
min-width: 150px;
}
+
.btn-register:hover {
background: #1e3a5f;
filter: brightness(1.5);
transform: translateY(-1px);
color: white;
}
+
.checkbox-section {
background-color: #f9fafb;
border-radius: 10px;
padding: 1.5rem;
margin: 1.5rem 0;
}
+
.form-check {
margin-bottom: 1rem;
}
+
.form-check-input {
margin-top: 0.1rem;
}
+
.form-check-label {
font-size: 0.9rem;
line-height: 1.4;
color: #374151;
}
-.form-control, .form-select {
+
+.form-control,
+.form-select {
border: 2px solid #d1d5db;
border-radius: 8px;
padding: 0.75rem 1rem;
@@ -100,45 +117,55 @@ body {
transition: border-color 0.3s ease;
margin-bottom: 0.5rem;
}
-.form-control:focus, .form-select:focus {
+
+.form-control:focus,
+.form-select:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25);
}
+
.form-control::placeholder {
color: #9ca3af;
font-size: 0.9rem;
}
+
.form-label {
font-weight: 500;
color: #374151;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
+
.main-container {
max-width: 600px;
margin: 2rem auto;
padding: 0 1rem;
}
+
.page-title {
color: #374151;
font-weight: 700;
font-size: 1.8rem;
margin-bottom: 1.5rem;
}
+
.password-input-group {
position: relative;
}
+
.password-section {
background-color: #f0f9ff;
border-radius: 10px;
padding: 1.5rem;
margin: 1.5rem 0;
}
+
.password-strength {
display: flex;
gap: 4px;
margin-top: 0.5rem;
}
+
.password-toggle {
position: absolute;
right: 12px;
@@ -151,30 +178,37 @@ body {
padding: 0;
font-size: 1rem;
}
+
.password-toggle:hover {
color: #3b82f6;
}
+
.phone-example {
color: #6b7280;
font-size: 0.9rem;
margin-bottom: 0.25rem;
}
+
.register-card {
background-color: white;
border-radius: 25px;
overflow: hidden;
- box-shadow: 0 4px 20px rgba(0,0,0,0.1);
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
+
.register-content {
padding: 2rem;
}
+
.required {
color: #dc3545;
}
+
.scales-icon {
font-size: 1.5rem;
margin-right: 0.75rem;
}
+
.section-title {
color: #374151;
font-weight: 600;
@@ -182,13 +216,16 @@ body {
margin-bottom: 1rem;
margin-top: 2rem;
}
+
.section-title-tight {
margin-top: 0 !important;
margin-bottom: 1rem !important;
}
+
.section-title:first-of-type {
margin-top: 0;
}
+
.strength-bar {
height: 4px;
flex: 1;
@@ -196,28 +233,36 @@ body {
border-radius: 2px;
transition: background-color 0.3s ease;
}
+
.strength-bar.active {
background-color: #10b981;
}
+
.strength-bar.medium {
background-color: #f59e0b;
}
+
.strength-bar.weak {
background-color: #ef4444;
}
+
@media (max-width: 768px) {
.main-container {
margin: 1rem auto;
padding: 0 0.5rem;
}
+
.register-content {
padding: 1.5rem;
}
+
.btn-container {
flex-direction: column;
align-items: center;
}
- .btn-register, .btn-back {
+
+ .btn-register,
+ .btn-back {
width: 100%;
max-width: 300px;
}
diff --git a/efile_app/efile/static/css/review.css b/efile_app/efile/static/css/review.css
index 473ce77..6099059 100644
--- a/efile_app/efile/static/css/review.css
+++ b/efile_app/efile/static/css/review.css
@@ -1,307 +1,333 @@
.review-container {
- max-width: 600px;
- margin: 0 auto;
- padding: 1rem;
+ max-width: 600px;
+ margin: 0 auto;
+ padding: 1rem;
}
+
.review-section {
- background: #f8f9fa;
- border-radius: 8px;
- padding: 1.5rem;
- margin-bottom: 1.5rem;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ background: #f8f9fa;
+ border-radius: 8px;
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
+
.review-section h3 {
- color: #2c5aa0;
- margin-bottom: 1rem;
- font-size: 1.3rem;
- font-weight: 600;
+ color: #2c5aa0;
+ margin-bottom: 1rem;
+ font-size: 1.3rem;
+ font-weight: 600;
}
+
.review-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0.75rem 0;
- border-bottom: 1px solid #e9ecef;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.75rem 0;
+ border-bottom: 1px solid #e9ecef;
}
+
.review-item:last-child {
- border-bottom: none;
+ border-bottom: none;
}
+
.review-label {
- font-weight: 500;
- color: #495057;
- flex: 0 0 auto;
+ font-weight: 500;
+ color: #495057;
+ flex: 0 0 auto;
}
+
.review-value {
- flex: 1;
- text-align: right;
- color: #212529;
- margin-left: 1rem;
+ flex: 1;
+ text-align: right;
+ color: #212529;
+ margin-left: 1rem;
}
.review-text {
- overflow: hidden;
- text-overflow: ellipsis;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
-.review-item > .d-flex.align-items-center.w-100 {
- justify-content: flex-end;
- gap: 0.75rem;
+.review-item>.d-flex.align-items-center.w-100 {
+ justify-content: flex-end;
+ gap: 0.75rem;
}
.review-item input.form-control {
- flex: 1 1 auto;
- min-width: 0;
+ flex: 1 1 auto;
+ min-width: 0;
}
.review-item .edit-btn {
- margin-left: 0.75rem;
- flex: 0 0 auto;
+ margin-left: 0.75rem;
+ flex: 0 0 auto;
}
+
.edit-btn {
- background: #45637d;
- color: white;
- border: none;
- border-radius: 20px;
- padding: 0.4rem 1rem;
- font-size: 0.85rem;
- text-decoration: none;
- display: inline-block;
- transition: background-color 0.3s;
+ background: #45637d;
+ color: white;
+ border: none;
+ border-radius: 20px;
+ padding: 0.4rem 1rem;
+ font-size: 0.85rem;
+ text-decoration: none;
+ display: inline-block;
+ transition: background-color 0.3s;
}
+
.edit-btn:hover {
- background: #5a6268;
- color: white;
+ background: #5a6268;
+ color: white;
}
+
.document-list {
- background: white;
- border: 1px solid #dee2e6;
- border-radius: 6px;
- padding: 1rem;
- margin-top: 1rem;
+ background: white;
+ border: 1px solid #dee2e6;
+ border-radius: 6px;
+ padding: 1rem;
+ margin-top: 1rem;
}
+
.document-item {
- display: flex;
- align-items: center;
- padding: 0.5rem 0;
- border-bottom: 1px solid #e9ecef;
+ display: flex;
+ align-items: center;
+ padding: 0.5rem 0;
+ border-bottom: 1px solid #e9ecef;
}
+
.document-item:last-child {
- border-bottom: none;
+ border-bottom: none;
}
+
.document-item i {
- color: #dc3545;
- margin-right: 0.5rem;
- font-size: 1.2rem;
+ color: #dc3545;
+ margin-right: 0.5rem;
+ font-size: 1.2rem;
}
+
.document-name {
- font-weight: 500;
- width: 62%;
- overflow: clip;
+ font-weight: 500;
+ width: 62%;
+ overflow: clip;
}
+
.change-btn {
- background: #6c757d;
- color: white;
- border: none;
- border-radius: 20px;
- padding: 0.3rem 0.8rem;
- font-size: 0.8rem;
- margin-left: auto;
+ background: #6c757d;
+ color: white;
+ border: none;
+ border-radius: 20px;
+ padding: 0.3rem 0.8rem;
+ font-size: 0.8rem;
+ margin-left: auto;
}
+
.change-btn:hover {
- background: #5a6268;
- color: white;
+ background: #5a6268;
+ color: white;
}
+
.action-buttons {
- margin-top: 2rem;
- display: flex;
- gap: 1rem;
- justify-content: center;
+ margin-top: 2rem;
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
}
+
.btn-continue {
- background: #2c5aa0;
- color: white;
- border: none;
- padding: 0.75rem 2rem;
- border-radius: 6px;
- font-weight: 600;
- font-size: 1rem;
+ background: #2c5aa0;
+ color: white;
+ border: none;
+ padding: 0.75rem 2rem;
+ border-radius: 6px;
+ font-weight: 600;
+ font-size: 1rem;
}
+
.btn-continue:hover {
- background: #1e3d6f;
- color: white;
+ background: #1e3d6f;
+ color: white;
}
+
.btn-back {
- background: #6c757d;
- color: white;
- border: none;
- padding: 0.75rem 2rem;
- border-radius: 6px;
- font-weight: 600;
- font-size: 1rem;
+ background: #6c757d;
+ color: white;
+ border: none;
+ padding: 0.75rem 2rem;
+ border-radius: 6px;
+ font-weight: 600;
+ font-size: 1rem;
}
+
.btn-back:hover {
- background: #5a6268;
- color: white;
+ background: #5a6268;
+ color: white;
}
+
.payment-info {
- background: #e3f2fd;
- border: 1px solid #bbdefb;
- border-radius: 6px;
- padding: 1rem;
- margin-top: 1rem;
+ background: #e3f2fd;
+ border: 1px solid #bbdefb;
+ border-radius: 6px;
+ padding: 1rem;
+ margin-top: 1rem;
}
+
.payment-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0.25rem 0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.25rem 0;
}
+
.payment-total {
- font-weight: bold;
- font-size: 1.1rem;
- border-top: 1px solid #90caf9;
- padding-top: 0.5rem;
- margin-top: 0.5rem;
+ font-weight: bold;
+ font-size: 1.1rem;
+ border-top: 1px solid #90caf9;
+ padding-top: 0.5rem;
+ margin-top: 0.5rem;
}
+
.loading-spinner {
- display: none;
- text-align: center;
- padding: 2rem;
+ display: none;
+ text-align: center;
+ padding: 2rem;
}
+
.error-message {
- display: none;
- background: #f8d7da;
- color: #721c24;
- border: 1px solid #f5c6cb;
- border-radius: 6px;
- padding: 1rem;
- margin-bottom: 1rem;
+ display: none;
+ background: #f8d7da;
+ color: #721c24;
+ border: 1px solid #f5c6cb;
+ border-radius: 6px;
+ padding: 1rem;
+ margin-bottom: 1rem;
}
+
.success-message {
- display: none;
- background: #d4edda;
- color: #155724;
- border: 1px solid #c3e6cb;
- border-radius: 6px;
- padding: 1rem;
- margin-bottom: 1rem;
+ display: none;
+ background: #d4edda;
+ color: #155724;
+ border: 1px solid #c3e6cb;
+ border-radius: 6px;
+ padding: 1rem;
+ margin-bottom: 1rem;
}
/* Payment methods styles moved from review.html */
.payment-methods-list {
- border: 1px solid #e0e0e0;
- border-radius: 8px;
- overflow: hidden;
- width: 100%;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ overflow: hidden;
+ width: 100%;
}
.payment-method-item {
- border-bottom: 1px solid #e0e0e0;
- padding: 0;
- width: 100%;
- box-sizing: border-box;
+ border-bottom: 1px solid #e0e0e0;
+ padding: 0;
+ width: 100%;
+ box-sizing: border-box;
}
.payment-method-item:last-child {
- border-bottom: none;
+ border-bottom: none;
}
.payment-method-item .form-check {
- margin: 0;
- padding: 16px 20px;
- display: flex;
- align-items: center;
- gap: 16px;
- width: 100%;
- box-sizing: border-box;
- min-height: 60px;
+ margin: 0;
+ padding: 16px 20px;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ width: 100%;
+ box-sizing: border-box;
+ min-height: 60px;
}
.payment-method-item .form-check-input {
- margin: 0 !important;
- margin-left: 0 !important;
- margin-right: 0 !important;
- flex: 0 0 20px;
- width: 20px;
- height: 20px;
- position: static;
+ margin: 0 !important;
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ flex: 0 0 20px;
+ width: 20px;
+ height: 20px;
+ position: static;
}
.payment-method-details {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 12px;
- flex: 1 1 auto;
- min-width: 0;
- overflow: hidden;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 12px;
+ flex: 1 1 auto;
+ min-width: 0;
+ overflow: hidden;
}
.payment-method-info {
- display: flex;
- align-items: center;
- color: #666;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- gap: 8px;
+ display: flex;
+ align-items: center;
+ color: #666;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ gap: 8px;
}
.payment-method-info i {
- margin: 0;
- width: 28px;
- text-align: center;
- color: #2c5aa0;
- font-size: 1.1rem;
+ margin: 0;
+ width: 28px;
+ text-align: center;
+ color: #2c5aa0;
+ font-size: 1.1rem;
}
.payment-method-name {
- font-weight: 600;
- color: #2c5aa0;
- margin-bottom: 0;
+ font-weight: 600;
+ color: #2c5aa0;
+ margin-bottom: 0;
}
.payment-method-holder {
- font-size: 0.9rem;
- color: #888;
+ font-size: 0.9rem;
+ color: #888;
}
.payment-method-item:hover {
- background-color: #f8f9fa;
+ background-color: #f8f9fa;
}
.no-payment-methods {
- text-align: center;
- padding: 2rem;
+ text-align: center;
+ padding: 2rem;
}
.add-payment-method {
- text-align: center;
+ text-align: center;
}
/* Disclaimer section styling */
.disclaimer-section {
- background: #fff3cd;
- border: 1px solid #ffeaa7;
- border-radius: 6px;
- padding: 1rem;
- margin-top: 2rem;
+ background: #fff3cd;
+ border: 1px solid #ffeaa7;
+ border-radius: 6px;
+ padding: 1rem;
+ margin-top: 2rem;
}
.disclaimer-section .form-check-label {
- font-size: 0.95rem;
- line-height: 1.4;
- color: #856404;
+ font-size: 0.95rem;
+ line-height: 1.4;
+ color: #856404;
}
.disclaimer-section .form-check-input {
- margin-top: 0.25rem;
+ margin-top: 0.25rem;
}
/* Disabled button styling */
.btn-continue:disabled {
- background: #6c757d !important;
- border-color: #6c757d !important;
- cursor: not-allowed;
- opacity: 0.65;
-}
+ background: #6c757d !important;
+ border-color: #6c757d !important;
+ cursor: not-allowed;
+ opacity: 0.65;
+}
\ No newline at end of file
diff --git a/efile_app/efile/static/css/upload.css b/efile_app/efile/static/css/upload.css
index cf8bde9..b46f47c 100644
--- a/efile_app/efile/static/css/upload.css
+++ b/efile_app/efile/static/css/upload.css
@@ -1,434 +1,435 @@
/* Upload Page Styles */
.upload-container {
- max-width: 800px;
- margin: 2rem auto;
- padding: 2rem;
- background: white;
- border-radius: 8px;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ max-width: 800px;
+ margin: 2rem auto;
+ padding: 2rem;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.section-header {
- color: #2c3e50;
- font-size: 2rem;
- font-weight: 600;
- margin-bottom: 1rem;
- text-align: left;
+ color: #2c3e50;
+ font-size: 2rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
+ text-align: left;
}
.section-description {
- color: #6c757d;
- font-size: 1.1rem;
- margin-bottom: 2rem;
+ color: #6c757d;
+ font-size: 1.1rem;
+ margin-bottom: 2rem;
}
.case-summary {
- padding: 1rem;
- border-top: 1px solid #e9ecef;
- border-bottom: 1px solid #e9ecef;
- border-left: 4px solid #007bff;
- border-right: 4px solid #007bff;
- border-radius: 6px;
- font-size: 0.95rem;
+ padding: 1rem;
+ border-top: 1px solid #e9ecef;
+ border-bottom: 1px solid #e9ecef;
+ border-left: 4px solid #007bff;
+ border-right: 4px solid #007bff;
+ border-radius: 6px;
+ font-size: 0.95rem;
}
.case-summary-header {
- font-size: 1.25rem;
- font-weight: 600;
- margin-bottom: 1rem;
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
}
.document-section {
- border: 1px solid #e9ecef;
- border-radius: 8px;
- padding: 1.5rem;
- background: #fff;
- margin-bottom: 1.5rem;
+ border: 1px solid #e9ecef;
+ border-radius: 8px;
+ padding: 1.5rem;
+ background: #fff;
+ margin-bottom: 1.5rem;
}
.document-label {
- font-size: 1.1rem;
- margin-bottom: 0.5rem;
+ font-size: 1.1rem;
+ margin-bottom: 0.5rem;
}
.required {
- color: #dc3545;
+ color: #dc3545;
}
.upload-area {
- position: relative;
- border: 2px dashed #dee2e6;
- border-radius: 8px;
- padding: 3rem 2rem;
- text-align: center;
- background: #fafbfc;
- transition: all 0.3s ease;
- cursor: pointer;
+ position: relative;
+ border: 2px dashed #dee2e6;
+ border-radius: 8px;
+ padding: 3rem 2rem;
+ text-align: center;
+ background: #fafbfc;
+ transition: all 0.3s ease;
+ cursor: pointer;
}
.upload-area:hover {
- border-color: #007bff;
- background: #f8f9fa;
+ border-color: #007bff;
+ background: #f8f9fa;
}
.upload-area.dragover {
- border-color: #007bff;
- background: #e3f2fd;
+ border-color: #007bff;
+ background: #e3f2fd;
}
.upload-placeholder {
- pointer-events: none;
+ pointer-events: none;
}
.file-input {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- opacity: 0;
- cursor: pointer;
- z-index: 1;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0;
+ cursor: pointer;
+ z-index: 1;
}
.file-preview {
- border: 1px solid #e9ecef;
- border-radius: 6px;
- padding: 1rem;
- margin-top: 0.75rem;
- background: #fff;
- position: relative;
+ border: 1px solid #e9ecef;
+ border-radius: 6px;
+ padding: 1rem;
+ margin-top: 0.75rem;
+ background: #fff;
+ position: relative;
}
.file-preview:first-child {
- margin-top: 0;
+ margin-top: 0;
}
.file-preview-lead {
- /* border: 1px solid #e9ecef; */
- border-radius: 6px;
- padding: 1rem;
- margin-top: 0.75rem;
- background: #fff;
- position: relative;
+ /* border: 1px solid #e9ecef; */
+ border-radius: 6px;
+ padding: 1rem;
+ margin-top: 0.75rem;
+ background: #fff;
+ position: relative;
}
.file-preview-lead:first-child {
- margin-top: 0;
+ margin-top: 0;
}
.file-info {
- /* overflow: clip; */
- word-break: break-all;
- display: flex;
- align-items: center;
- gap: 0.75rem;
- padding-right: 2rem; /* Make room for the X button */
+ /* overflow: clip; */
+ word-break: break-all;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding-right: 2rem;
+ /* Make room for the X button */
}
.file-info i.fa-file-pdf {
- color: #dc3545;
- font-size: 1.5rem;
- flex-shrink: 0;
+ color: #dc3545;
+ font-size: 1.5rem;
+ flex-shrink: 0;
}
.file-remove {
- position: absolute;
- top: 0.75rem;
- right: 0.75rem;
- color: #6c757d;
- cursor: pointer;
- padding: 0.25rem;
- border-radius: 4px;
- transition: color 0.2s, background-color 0.2s;
- font-size: 1.2rem;
- line-height: 1;
- width: 24px;
- height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: none;
- border: none;
- z-index: 20;
+ position: absolute;
+ top: 0.75rem;
+ right: 0.75rem;
+ color: #6c757d;
+ cursor: pointer;
+ padding: 0.25rem;
+ border-radius: 4px;
+ transition: color 0.2s, background-color 0.2s;
+ font-size: 1.2rem;
+ line-height: 1;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: none;
+ z-index: 20;
}
.file-remove:hover {
- color: #dc3545;
- background-color: #f8f9fa;
+ color: #dc3545;
+ background-color: #f8f9fa;
}
.requirements-section {
- background: #f8f9fa;
- padding: 1.5rem;
- border-radius: 6px;
- border: 1px solid #e9ecef;
+ background: #f8f9fa;
+ padding: 1.5rem;
+ border-radius: 6px;
+ border: 1px solid #e9ecef;
}
.requirements-section h5 {
- color: #495057;
- margin-bottom: 1rem;
- font-size: 1.1rem;
+ color: #495057;
+ margin-bottom: 1rem;
+ font-size: 1.1rem;
}
.requirement-item {
- margin-bottom: 0.75rem;
- padding-left: 1rem;
- position: relative;
+ margin-bottom: 0.75rem;
+ padding-left: 1rem;
+ position: relative;
}
.requirement-item:before {
- content: "•";
- color: #007bff;
- font-weight: bold;
- position: absolute;
- left: 0;
+ content: "•";
+ color: #007bff;
+ font-weight: bold;
+ position: absolute;
+ left: 0;
}
.document-options {
- background: #fff;
- border: 1px solid #e9ecef;
- border-radius: 8px;
- padding: 1.5rem;
- margin-top: 1rem;
+ background: #fff;
+ border: 1px solid #e9ecef;
+ border-radius: 8px;
+ padding: 1.5rem;
+ margin-top: 1rem;
}
.document-options h5 {
- color: #495057;
- margin-bottom: 1rem;
+ color: #495057;
+ margin-bottom: 1rem;
}
.document-options h6 {
- color: #6c757d;
- margin-bottom: 1rem;
- font-size: 1rem;
- font-weight: 600;
+ color: #6c757d;
+ margin-bottom: 1rem;
+ font-size: 1rem;
+ font-weight: 600;
}
.supporting-document-options {
- background: #f8f9fa;
- border: 1px solid #dee2e6;
- border-radius: 6px;
- padding: 1rem;
- margin-bottom: 1rem;
+ background: #f8f9fa;
+ border: 1px solid #dee2e6;
+ border-radius: 6px;
+ padding: 1rem;
+ margin-bottom: 1rem;
}
.supporting-document-options h6 {
- color: #495057;
- font-size: 0.9rem;
- margin-bottom: 0.75rem;
- font-weight: 600;
- border-bottom: 1px solid #e9ecef;
- padding-bottom: 0.5rem;
- word-break: break-word;
+ color: #495057;
+ font-size: 0.9rem;
+ margin-bottom: 0.75rem;
+ font-weight: 600;
+ border-bottom: 1px solid #e9ecef;
+ padding-bottom: 0.5rem;
+ word-break: break-word;
}
.upload-progress {
- margin: 2rem 0;
+ margin: 2rem 0;
}
.progress {
- height: 8px;
- background-color: #e9ecef;
+ height: 8px;
+ background-color: #e9ecef;
}
.progress-bar {
- background-color: #007bff;
- transition: width 0.3s ease;
+ background-color: #007bff;
+ transition: width 0.3s ease;
}
.alert {
- border-radius: 6px;
- padding: 1rem;
- margin-bottom: 1rem;
+ border-radius: 6px;
+ padding: 1rem;
+ margin-bottom: 1rem;
}
.alert-warning {
- background-color: #fff3cd;
- border-color: #ffeaa7;
- color: #856404;
+ background-color: #fff3cd;
+ border-color: #ffeaa7;
+ color: #856404;
}
.alert-danger {
- background-color: #f8d7da;
- border-color: #f5c6cb;
- color: #721c24;
+ background-color: #f8d7da;
+ border-color: #f5c6cb;
+ color: #721c24;
}
.alert-success {
- background-color: #d4edda;
- border-color: #c3e6cb;
- color: #155724;
+ background-color: #d4edda;
+ border-color: #c3e6cb;
+ color: #155724;
}
/* Upload Status Indicators */
.upload-status {
- margin-top: 0.5rem;
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- background: #f8f9fa;
- font-size: 0.875rem;
- display: flex;
- align-items: center;
- clear: both;
- position: relative;
- z-index: 10;
+ margin-top: 0.5rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+ background: #f8f9fa;
+ font-size: 0.875rem;
+ display: flex;
+ align-items: center;
+ clear: both;
+ position: relative;
+ z-index: 10;
}
.upload-status i.fa-spinner {
- color: #007bff;
+ color: #007bff;
}
.upload-status i.fa-check-circle {
- color: #28a745;
+ color: #28a745;
}
.upload-status i.fa-exclamation-triangle {
- color: #dc3545;
+ color: #dc3545;
}
/* Button Styles */
.btn {
- padding: 0.75rem 1.5rem;
- border-radius: 6px;
- font-weight: 500;
- text-decoration: none;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- border: none;
- cursor: pointer;
- transition: all 0.3s ease;
- text-align: center;
+ padding: 0.75rem 1.5rem;
+ border-radius: 6px;
+ font-weight: 500;
+ text-decoration: none;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ text-align: center;
}
.btn-primary {
- background-color: #007bff;
- color: white;
+ background-color: #007bff;
+ color: white;
}
.btn-primary:hover {
- background-color: #0056b3;
- color: white;
+ background-color: #0056b3;
+ color: white;
}
.btn-outline-secondary {
- background-color: transparent;
- color: #6c757d;
- border: 1px solid #6c757d;
+ background-color: transparent;
+ color: #6c757d;
+ border: 1px solid #6c757d;
}
.btn-outline-secondary:hover {
- background-color: #6c757d;
- color: white;
+ background-color: #6c757d;
+ color: white;
}
.btn:disabled {
- opacity: 0.6;
- cursor: not-allowed;
+ opacity: 0.6;
+ cursor: not-allowed;
}
@media (max-width: 768px) {
- .upload-container {
- margin: 1rem;
- padding: 1rem;
- }
+ .upload-container {
+ margin: 1rem;
+ padding: 1rem;
+ }
- .section-header {
- font-size: 1.5rem;
- }
+ .section-header {
+ font-size: 1.5rem;
+ }
- .upload-area {
- padding: 2rem 1rem;
- }
+ .upload-area {
+ padding: 2rem 1rem;
+ }
- .lead-preview-area {
- padding: 0rem 0rem;
- }
+ .lead-preview-area {
+ padding: 0rem 0rem;
+ }
- .case-summary .row {
- flex-direction: column;
- }
+ .case-summary .row {
+ flex-direction: column;
+ }
- .case-summary .col-md-4 {
- margin-bottom: 0.5rem;
- }
+ .case-summary .col-md-4 {
+ margin-bottom: 0.5rem;
+ }
- .d-flex.gap-2 .btn {
- flex: 1;
- min-width: 0;
- white-space: nowrap;
- }
+ .d-flex.gap-2 .btn {
+ flex: 1;
+ min-width: 0;
+ white-space: nowrap;
+ }
}
@media (max-width: 576px) {
- .upload-container {
- margin: 0.5rem;
- padding: 0.75rem;
- }
+ .upload-container {
+ margin: 0.5rem;
+ padding: 0.75rem;
+ }
- .btn {
- width: 100%;
- margin-bottom: 0.5rem;
- text-align: center;
- justify-content: center;
- }
+ .btn {
+ width: 100%;
+ margin-bottom: 0.5rem;
+ text-align: center;
+ justify-content: center;
+ }
- .d-flex.flex-column .btn {
- margin-bottom: 0.5rem;
- }
+ .d-flex.flex-column .btn {
+ margin-bottom: 0.5rem;
+ }
- .d-flex.flex-column .btn:last-child {
- margin-bottom: 0;
- }
+ .d-flex.flex-column .btn:last-child {
+ margin-bottom: 0;
+ }
- .d-flex.flex-column.flex-md-row.justify-content-between {
- align-items: center;
- text-align: center;
- }
+ .d-flex.flex-column.flex-md-row.justify-content-between {
+ align-items: center;
+ text-align: center;
+ }
- .gap-2 > * + * {
- margin-top: 0.5rem !important;
- }
+ .gap-2>*+* {
+ margin-top: 0.5rem !important;
+ }
- .gap-3 > * + * {
- margin-top: 1rem !important;
- }
+ .gap-3>*+* {
+ margin-top: 1rem !important;
+ }
}
.was-validated .form-control:invalid {
- border-color: #dc3545;
+ border-color: #dc3545;
}
.was-validated .form-control:valid {
- border-color: #28a745;
+ border-color: #28a745;
}
.invalid-feedback {
- display: block;
- color: #dc3545;
- font-size: 0.875rem;
- margin-top: 0.25rem;
+ display: block;
+ color: #dc3545;
+ font-size: 0.875rem;
+ margin-top: 0.25rem;
}
.requirements-section,
.document-options,
.alert {
- margin: 0 auto 1.5rem auto;
+ margin: 0 auto 1.5rem auto;
}
.requirements-section h5,
.document-options h5 {
- margin-bottom: 1.5rem;
- color: #2c3e50;
+ margin-bottom: 1.5rem;
+ color: #2c3e50;
}
.d-flex.justify-content-between,
.d-flex.flex-column.flex-md-row.justify-content-between {
- margin-top: 2rem;
+ margin-top: 2rem;
}
.d-flex.flex-column.flex-md-row.justify-content-between .btn {
- min-width: 200px;
-}
+ min-width: 200px;
+}
\ No newline at end of file
diff --git a/efile_app/efile/static/js/api-utils.js b/efile_app/efile/static/js/api-utils.js
index 8dcf742a..faf62cd 100644
--- a/efile_app/efile/static/js/api-utils.js
+++ b/efile_app/efile/static/js/api-utils.js
@@ -9,7 +9,7 @@ class ApiUtils {
this.cache = this.getCache();
//this.cacheExpiry = 0; // Use for Development
this.cacheExpiry = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
-
+
// Clear expired cache entries on initialization
this.clearExpiredCache();
}
@@ -19,15 +19,15 @@ class ApiUtils {
* @returns {string} Current jurisdiction code
*/
getCurrentJurisdiction() {
- // Try to get from jurisdiction selector first
- const jurisdiction = document.getElementById("currentJurisdiction");
- if (jurisdiction && jurisdiction.textContent) {
- return jurisdiction.textContent;
- }
+ // Try to get from jurisdiction selector first
+ const jurisdiction = document.getElementById("currentJurisdiction");
+ if (jurisdiction && jurisdiction.textContent) {
+ return jurisdiction.textContent;
+ }
- // Default to null if nothing found; rather that than weird bugs from a default state
- console.warn("Returning null for current, likely shouldn't")
- return null;
+ // Default to null if nothing found; rather that than weird bugs from a default state
+ console.warn("Returning null for current, likely shouldn't")
+ return null;
}
getCache() {
@@ -68,7 +68,7 @@ class ApiUtils {
getCachedResponse(endpoint, params = {}) {
const cacheKey = this.getCacheKey(endpoint, params);
const cacheEntry = this.cache[cacheKey];
-
+
if (this.isCacheValid(cacheEntry)) {
const age = Date.now() - cacheEntry.timestamp;
return cacheEntry.data;
@@ -102,13 +102,15 @@ class ApiUtils {
try {
const response = await fetch('/api/csrf-token/', {
method: 'GET',
- headers: { 'X-Requested-With': 'XMLHttpRequest' }
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest'
+ }
});
-
+
if (response.ok) {
const data = await response.json();
this.csrfToken = data.csrf_token;
-
+
// Update the token in the form if it exists
const tokenInput = document.querySelector('[name=csrfmiddlewaretoken]');
if (tokenInput) {
@@ -159,18 +161,21 @@ class ApiUtils {
async makeRequest(endpoint, options = {}) {
const {
method = 'GET',
- params = {},
- data = null,
- headers = {},
- timeout = 30000
+ params = {},
+ data = null,
+ headers = {},
+ timeout = 30000
} = options;
try {
const url = this.buildUrl(endpoint, params);
-
+
const requestOptions = {
method,
- headers: { ...this.getDefaultHeaders(), ...headers }
+ headers: {
+ ...this.getDefaultHeaders(),
+ ...headers
+ }
};
if (data && method !== 'GET') {
@@ -202,10 +207,15 @@ class ApiUtils {
}
async fetchJSON(endpoint, method = 'GET', params = {}, data = null) {
- return this.makeRequest(endpoint, {method, params, data, headers: {
- 'Content-Type': 'application/json',
- 'X-CSRFToken': this.getCSRFToken(),
- }});
+ return this.makeRequest(endpoint, {
+ method,
+ params,
+ data,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': this.getCSRFToken(),
+ }
+ });
}
handleApiError(error) {
@@ -236,13 +246,13 @@ class ApiUtils {
}
// Convenience methods for common HTTP verbs
- async get(endpoint, params = {}, use_csrf=false) {
+ async get(endpoint, params = {}, use_csrf = false) {
// Check cache first
const cachedResponse = this.getCachedResponse(endpoint, params);
if (cachedResponse !== null) {
return cachedResponse;
}
-
+
let headers = {};
if (use_csrf) {
headers = {
@@ -252,42 +262,45 @@ class ApiUtils {
}
// Make API request if not cached
- const response = await this.makeRequest(endpoint, { params, headers });
-
+ const response = await this.makeRequest(endpoint, {
+ params,
+ headers
+ });
+
// Cache the response
this.setCachedResponse(endpoint, params, response);
-
+
return response;
}
async post(endpoint, data = {}, params = {}) {
- return this.makeRequest(endpoint, {
- method: 'POST',
- data,
- params
+ return this.makeRequest(endpoint, {
+ method: 'POST',
+ data,
+ params
});
}
async put(endpoint, data = {}, params = {}) {
- return this.makeRequest(endpoint, {
- method: 'PUT',
- data,
- params
+ return this.makeRequest(endpoint, {
+ method: 'PUT',
+ data,
+ params
});
}
async patch(endpoint, data = {}, params = {}) {
- return this.makeRequest(endpoint, {
- method: 'PATCH',
- data,
- params
+ return this.makeRequest(endpoint, {
+ method: 'PATCH',
+ data,
+ params
});
}
async delete(endpoint, params = {}) {
- return this.makeRequest(endpoint, {
- method: 'DELETE',
- params
+ return this.makeRequest(endpoint, {
+ method: 'DELETE',
+ params
});
}
@@ -343,7 +356,7 @@ class ApiUtils {
clearExpiredCache() {
const now = Date.now();
let cleared = 0;
-
+
Object.keys(this.cache).forEach(key => {
const entry = this.cache[key];
if (!this.isCacheValid(entry)) {
@@ -351,7 +364,7 @@ class ApiUtils {
cleared++;
}
});
-
+
if (cleared > 0) {
this.saveCache();
}
@@ -371,13 +384,13 @@ class ApiUtils {
const entry = this.cache[key];
const isValid = this.isCacheValid(entry);
const age = Date.now() - entry.timestamp;
-
+
if (isValid) {
stats.validEntries++;
} else {
stats.expiredEntries++;
}
-
+
stats.entries.push({
key: key.substring(0, 50) + (key.length > 50 ? '...' : ''),
ageMinutes: Math.round(age / 60000),
@@ -407,8 +420,11 @@ window.getCacheData = () => apiUtils.cache;
// Export for module use or make globally available
if (typeof module !== 'undefined' && module.exports) {
- module.exports = { ApiUtils, apiUtils };
+ module.exports = {
+ ApiUtils,
+ apiUtils
+ };
} else {
window.ApiUtils = ApiUtils;
window.apiUtils = apiUtils;
-}
+}
\ No newline at end of file
diff --git a/efile_app/efile/static/js/cascading-dropdowns.js b/efile_app/efile/static/js/cascading-dropdowns.js
index 8e7523f..2b27086 100644
--- a/efile_app/efile/static/js/cascading-dropdowns.js
+++ b/efile_app/efile/static/js/cascading-dropdowns.js
@@ -3,629 +3,633 @@
* Features: Location-based recommendations, court-specific filtering, progressive form enablement
*/
class CascadingDropdowns {
- constructor() {
- this.dropdownMapping = {
- court: {
- next: "case_category",
- endpoint: "/api/dropdowns/case-categories/",
- },
- case_category: {
- next: "case_type",
- endpoint: "/api/dropdowns/case-types/",
- },
- case_type: {
- next: "party_type", // Case type is now the final dropdown on expert form
- endpoint: "/api/dropdowns/party-types",
- },
- party_type: {
- next: null,
- endpoint: null
- }
- };
-
- this.userProfile = null;
- this.selectedValues = {
- court: null,
- case_category: null,
- case_type: null,
- party_type: null,
- };
- this.optionalServicesLoaded = false;
- this.isAutomaticSelection = false; // Track if selection is automatic
- }
-
- async init() {
- // Load user profile first
- await this.loadUserProfile();
- await this.loadGuesses();
-
- // Load initial data for independent dropdowns with user context
- await this.loadCourtsWithUserContext();
-
- // Add event listeners
- document.addEventListener("change", (e) => {
- if (e.target.classList.contains("dropdown-field")) {
- this.handleDropdownChange(e.target);
- }
- });
- }
-
- async loadCourtsWithUserContext() {
- const params = {};
- const currentJurisdiction = apiUtils.getCurrentJurisdiction();
-
- // Set the jurisdiction parameter
- params.jurisdiction = currentJurisdiction;
- params.guessed_court = this.guesses?.court;
-
- // For Massachusetts, skip location-based filtering and show all courts
- if (currentJurisdiction === 'massachusetts') {
- await this.loadDropdownData("court", "/api/dropdowns/courts/", params);
- return;
- }
-
- // For other jurisdictions (like Illinois), use location-based recommendations
- if (this.userProfile) {
- // Pass user location info to courts API
- if (this.userProfile.preferred_county) {
- params.user_county = this.userProfile.preferred_county;
- }
- if (this.userProfile.zip_code) {
- params.user_zip = this.userProfile.zip_code;
- }
- // Add jurisdiction if available from profile
- if (this.userProfile.state) {
- const jurisdictionMap = {
- IL: "illinois",
- Illinois: "illinois",
+ constructor() {
+ this.dropdownMapping = {
+ court: {
+ next: "case_category",
+ endpoint: "/api/dropdowns/case-categories/",
+ },
+ case_category: {
+ next: "case_type",
+ endpoint: "/api/dropdowns/case-types/",
+ },
+ case_type: {
+ next: "party_type", // Case type is now the final dropdown on expert form
+ endpoint: "/api/dropdowns/party-types",
+ },
+ party_type: {
+ next: null,
+ endpoint: null
+ }
};
- const profileJurisdiction = jurisdictionMap[this.userProfile.state] || "illinois";
- // Only override if current jurisdiction matches profile
- if (currentJurisdiction === "illinois" || !currentJurisdiction) {
- params.jurisdiction = profileJurisdiction;
- }
- }
- } else {
- console.warn("No user profile available for courts loading");
+
+ this.userProfile = null;
+ this.selectedValues = {
+ court: null,
+ case_category: null,
+ case_type: null,
+ party_type: null,
+ };
+ this.optionalServicesLoaded = false;
+ this.isAutomaticSelection = false; // Track if selection is automatic
}
- await this.loadDropdownData("court", "/api/dropdowns/courts/", params);
- }
- async loadUserProfile() {
- const statusElement = document.getElementById("userProfileStatus");
+ async init() {
+ // Load user profile first
+ await this.loadUserProfile();
+ await this.loadGuesses();
- try {
- if (statusElement) {
- statusElement.style.display = "block";
- statusElement.innerHTML =
- ' Loading your information...';
- }
+ // Load initial data for independent dropdowns with user context
+ await this.loadCourtsWithUserContext();
- const response = await this.makeRequest("/api/auth/profile/", {"jurisdiction": apiUtils.getCurrentJurisdiction()});
+ // Add event listeners
+ document.addEventListener("change", (e) => {
+ if (e.target.classList.contains("dropdown-field")) {
+ this.handleDropdownChange(e.target);
+ }
+ });
+ }
+
+ async loadCourtsWithUserContext() {
+ const params = {};
+ const currentJurisdiction = apiUtils.getCurrentJurisdiction();
+
+ // Set the jurisdiction parameter
+ params.jurisdiction = currentJurisdiction;
+ params.guessed_court = this.guesses?.court;
- if (response.success) {
- if (statusElement) {
- statusElement.style.display = "none";
+ // For Massachusetts, skip location-based filtering and show all courts
+ if (currentJurisdiction === 'massachusetts') {
+ await this.loadDropdownData("court", "/api/dropdowns/courts/", params);
+ return;
}
- this.userProfile = response.data;
- } else {
- console.warn("❌ Failed to load user profile:", response.error);
- if (statusElement) {
- statusElement.className = "alert alert-warning";
- statusElement.innerHTML =
- ' Could not load profile information. Using default settings.';
- setTimeout(() => {
- statusElement.style.display = "none";
- }, 3000);
+
+ // For other jurisdictions (like Illinois), use location-based recommendations
+ if (this.userProfile) {
+ // Pass user location info to courts API
+ if (this.userProfile.preferred_county) {
+ params.user_county = this.userProfile.preferred_county;
+ }
+ if (this.userProfile.zip_code) {
+ params.user_zip = this.userProfile.zip_code;
+ }
+ // Add jurisdiction if available from profile
+ if (this.userProfile.state) {
+ const jurisdictionMap = {
+ IL: "illinois",
+ Illinois: "illinois",
+ };
+ const profileJurisdiction = jurisdictionMap[this.userProfile.state] || "illinois";
+ // Only override if current jurisdiction matches profile
+ if (currentJurisdiction === "illinois" || !currentJurisdiction) {
+ params.jurisdiction = profileJurisdiction;
+ }
+ }
+ } else {
+ console.warn("No user profile available for courts loading");
}
- }
- } catch (error) {
- console.error("❌ Error loading user profile:", error);
- if (statusElement) {
- statusElement.className = "alert alert-warning";
- statusElement.innerHTML =
- ' Could not load profile information. Using default settings.';
- setTimeout(() => {
- statusElement.style.display = "none";
- }, 3000);
- }
+ await this.loadDropdownData("court", "/api/dropdowns/courts/", params);
}
- }
- async loadGuesses() {
- const data = await apiUtils.getUploadData();
- this.guesses = data['guesses']
- }
+ async loadUserProfile() {
+ const statusElement = document.getElementById("userProfileStatus");
+
+ try {
+ if (statusElement) {
+ statusElement.style.display = "block";
+ statusElement.innerHTML =
+ ' Loading your information...';
+ }
- async loadDropdownData(fieldId, endpoint, params = {}) {
- const dropdown = document.getElementById(fieldId);
+ const response = await this.makeRequest("/api/auth/profile/", {
+ "jurisdiction": apiUtils.getCurrentJurisdiction()
+ });
- if (!dropdown) {
- console.error(`Dropdown with ID '${fieldId}' not found in DOM`);
- return;
+ if (response.success) {
+ if (statusElement) {
+ statusElement.style.display = "none";
+ }
+ this.userProfile = response.data;
+ } else {
+ console.warn("❌ Failed to load user profile:", response.error);
+ if (statusElement) {
+ statusElement.className = "alert alert-warning";
+ statusElement.innerHTML =
+ ' Could not load profile information. Using default settings.';
+ setTimeout(() => {
+ statusElement.style.display = "none";
+ }, 3000);
+ }
+ }
+ } catch (error) {
+ console.error("❌ Error loading user profile:", error);
+ if (statusElement) {
+ statusElement.className = "alert alert-warning";
+ statusElement.innerHTML =
+ ' Could not load profile information. Using default settings.';
+ setTimeout(() => {
+ statusElement.style.display = "none";
+ }, 3000);
+ }
+ }
}
- const loader = document.getElementById(`loading-${dropdown.dataset.level}`);
+ async loadGuesses() {
+ const data = await apiUtils.getUploadData();
+ this.guesses = data['guesses']
+ }
- try {
- this.showLoader(loader);
- this.clearDropdown(dropdown);
+ async loadDropdownData(fieldId, endpoint, params = {}) {
+ const dropdown = document.getElementById(fieldId);
- const response = await this.makeRequest(endpoint, params);
+ if (!dropdown) {
+ console.error(`Dropdown with ID '${fieldId}' not found in DOM`);
+ return;
+ }
- if (response.success) {
- // Check if we have valid data
- if (
- response.data &&
- Array.isArray(response.data) &&
- response.data.length > 0
- ) {
- this.populateDropdown(dropdown, response.data);
- dropdown.parentElement.removeAttribute("hidden");
- dropdown.disabled = false;
- if (response.data.length == 1 && dropdown.id =="party_type") {
- dropdown.parentElement.hidden = true;
- dropdown.value = response.data[0].value || response.data[0].id;
- }
- } else {
- console.warn(`No data returned for ${fieldId}:`, response);
- this.showError(dropdown, "No options available for this selection");
+ const loader = document.getElementById(`loading-${dropdown.dataset.level}`);
+
+ try {
+ this.showLoader(loader);
+ this.clearDropdown(dropdown);
+
+ const response = await this.makeRequest(endpoint, params);
+
+ if (response.success) {
+ // Check if we have valid data
+ if (
+ response.data &&
+ Array.isArray(response.data) &&
+ response.data.length > 0
+ ) {
+ this.populateDropdown(dropdown, response.data);
+ dropdown.parentElement.removeAttribute("hidden");
+ dropdown.disabled = false;
+ if (response.data.length == 1 && dropdown.id == "party_type") {
+ dropdown.parentElement.hidden = true;
+ dropdown.value = response.data[0].value || response.data[0].id;
+ }
+ } else {
+ console.warn(`No data returned for ${fieldId}:`, response);
+ this.showError(dropdown, "No options available for this selection");
+ }
+ } else {
+ console.error(`API error for ${fieldId}:`, response);
+ this.showError(dropdown, response.error || "Failed to load options");
+ }
+ } catch (error) {
+ console.error(`Error loading dropdown data for ${fieldId}:`, error);
+ this.showError(dropdown, "Network error occurred");
+ } finally {
+ this.hideLoader(loader);
}
- } else {
- console.error(`API error for ${fieldId}:`, response);
- this.showError(dropdown, response.error || "Failed to load options");
- }
- } catch (error) {
- console.error(`Error loading dropdown data for ${fieldId}:`, error);
- this.showError(dropdown, "Network error occurred");
- } finally {
- this.hideLoader(loader);
}
- }
-
- async makeRequest(endpoint, params = {}) {
- return await apiUtils.get(endpoint, params);
- }
-
- handleDropdownChange(dropdown) {
- const fieldId = dropdown.id;
- const selectedValue = dropdown.value;
- const mapping = this.dropdownMapping[fieldId];
- // Only clear recommendation notices if the court dropdown is manually changed by user
- // Preserve notices for automatic selections and when other dropdowns change
- if (fieldId === "court" && !this.isAutomaticSelection) {
- this.clearAllRecommendationNotices();
+ async makeRequest(endpoint, params = {}) {
+ return await apiUtils.get(endpoint, params);
}
- // Reset the automatic selection flag after handling
- this.isAutomaticSelection = false;
+ handleDropdownChange(dropdown) {
+ const fieldId = dropdown.id;
+ const selectedValue = dropdown.value;
+ const mapping = this.dropdownMapping[fieldId];
- // Store the selected value
- this.selectedValues[fieldId] = selectedValue;
+ // Only clear recommendation notices if the court dropdown is manually changed by user
+ // Preserve notices for automatic selections and when other dropdowns change
+ if (fieldId === "court" && !this.isAutomaticSelection) {
+ this.clearAllRecommendationNotices();
+ }
- // Reset all dependent dropdowns when this dropdown changes
- this.resetDependentDropdowns(fieldId);
+ // Reset the automatic selection flag after handling
+ this.isAutomaticSelection = false;
- // Reset optional services flag when case type changes
- if (
- fieldId === "case_type" ||
- fieldId === "case_category" ||
- fieldId === "court"
- ) {
- this.optionalServicesLoaded = false;
- }
+ // Store the selected value
+ this.selectedValues[fieldId] = selectedValue;
- if (mapping && selectedValue) {
- // Prepare parameters for the next dropdown
- let params = {};
-
- // Always add jurisdiction
- params.jurisdiction = apiUtils.getCurrentJurisdiction();
-
- // Add additional context parameters based on the field
- if (fieldId === "court") {
- // When court is selected, load case categories for that court
- params.court = selectedValue;
- } else if (fieldId === "case_category") {
- // Case category to case type
- params.parent = selectedValue; // Suffolk API expects parent parameter
- if (this.selectedValues.court) {
- params.court = this.selectedValues.court;
- } else {
- console.warn("No court selected when trying to load case types");
- return;
- }
- } else if (fieldId === "case_type") {
- params.parent = selectedValue; // Suffolk API expects parent parameter for case_type
- params.case_type = selectedValue;
- if (this.selectedValues.court) {
- params.court = this.selectedValues.court;
- } else {
- console.warn("No court selected when trying to load filing types");
- return;
- }
-
- // Add both existing_case and initial parameters for filing type endpoint
- const existingCase = sessionStorage.getItem('existing_case') || 'no';
- const isInitialFiling = existingCase === 'no';
- params.existing_case = existingCase; // Pass existing_case to Django API
- params.initial = isInitialFiling ? 'true' : 'false'; // Pass initial to Suffolk API
- }
-
- // Validate required parameters before making API call
- if (this.validateParameters(fieldId, params)) {
- // Load data for the next dropdown only if there is a next dropdown
- if (mapping.next && mapping.endpoint) {
- params.guessed_case_category = this.guesses['case category'];
- params.guessed_case_type = this.guesses['case type'];
- params.only_required = true;
- this.loadDropdownData(mapping.next, mapping.endpoint, params);
+ // Reset all dependent dropdowns when this dropdown changes
+ this.resetDependentDropdowns(fieldId);
+
+ // Reset optional services flag when case type changes
+ if (
+ fieldId === "case_type" ||
+ fieldId === "case_category" ||
+ fieldId === "court"
+ ) {
+ this.optionalServicesLoaded = false;
}
- } else {
- console.warn(`Missing required parameters for ${fieldId}:`, params);
- return;
- }
-
- // Special handling for court selection
- if (fieldId === "court") {
- // Clear the user profile status indicator when court changes
- const statusElement = document.getElementById("userProfileStatus");
- if (statusElement) {
- statusElement.style.display = "none";
- statusElement.className = "alert alert-info"; // Reset to default class
- statusElement.innerHTML =
- ' Loading your information...';
+
+ if (mapping && selectedValue) {
+ // Prepare parameters for the next dropdown
+ let params = {};
+
+ // Always add jurisdiction
+ params.jurisdiction = apiUtils.getCurrentJurisdiction();
+
+ // Add additional context parameters based on the field
+ if (fieldId === "court") {
+ // When court is selected, load case categories for that court
+ params.court = selectedValue;
+ } else if (fieldId === "case_category") {
+ // Case category to case type
+ params.parent = selectedValue; // Suffolk API expects parent parameter
+ if (this.selectedValues.court) {
+ params.court = this.selectedValues.court;
+ } else {
+ console.warn("No court selected when trying to load case types");
+ return;
+ }
+ } else if (fieldId === "case_type") {
+ params.parent = selectedValue; // Suffolk API expects parent parameter for case_type
+ params.case_type = selectedValue;
+ if (this.selectedValues.court) {
+ params.court = this.selectedValues.court;
+ } else {
+ console.warn("No court selected when trying to load filing types");
+ return;
+ }
+
+ // Add both existing_case and initial parameters for filing type endpoint
+ const existingCase = sessionStorage.getItem('existing_case') || 'no';
+ const isInitialFiling = existingCase === 'no';
+ params.existing_case = existingCase; // Pass existing_case to Django API
+ params.initial = isInitialFiling ? 'true' : 'false'; // Pass initial to Suffolk API
+ }
+
+ // Validate required parameters before making API call
+ if (this.validateParameters(fieldId, params)) {
+ // Load data for the next dropdown only if there is a next dropdown
+ if (mapping.next && mapping.endpoint) {
+ params.guessed_case_category = this.guesses['case category'];
+ params.guessed_case_type = this.guesses['case type'];
+ params.only_required = true;
+ this.loadDropdownData(mapping.next, mapping.endpoint, params);
+ }
+ } else {
+ console.warn(`Missing required parameters for ${fieldId}:`, params);
+ return;
+ }
+
+ // Special handling for court selection
+ if (fieldId === "court") {
+ // Clear the user profile status indicator when court changes
+ const statusElement = document.getElementById("userProfileStatus");
+ if (statusElement) {
+ statusElement.style.display = "none";
+ statusElement.className = "alert alert-info"; // Reset to default class
+ statusElement.innerHTML =
+ ' Loading your information...';
+ }
+
+ // Only clear recommendation notices and visual indicators if the user manually changed the court
+ // We'll preserve these for auto-selections
+ this.clearAllDropdownVisualIndicators();
+
+ const caseCategoryDropdown = document.getElementById("case_category");
+ if (caseCategoryDropdown) {
+ caseCategoryDropdown.disabled = false;
+ // Update placeholder text
+ const placeholder =
+ caseCategoryDropdown.querySelector('option[value=""]');
+ if (placeholder) {
+ placeholder.textContent = gettext("Select Case Category");
+ }
+ }
+ }
+
+ // Trigger dynamic form sections when case type changes
+ if (fieldId === "case_type") {
+ this.triggerDynamicFormSections();
+ }
+
+ // Re-render dynamic form sections when court changes (if they already exist)
+ // This ensures conditional requirements are re-evaluated
+ if (fieldId === "court" && this.selectedValues.case_type) {
+ this.triggerDynamicFormSections();
+ }
+ } else if (mapping) {
+ // Clear and disable the next dropdown if no value selected
+ const nextDropdown = document.getElementById(mapping.next);
+ if (nextDropdown) {
+ this.clearDropdown(nextDropdown);
+ nextDropdown.disabled = true;
+ }
}
- // Only clear recommendation notices and visual indicators if the user manually changed the court
- // We'll preserve these for auto-selections
- this.clearAllDropdownVisualIndicators();
-
- const caseCategoryDropdown = document.getElementById("case_category");
- if (caseCategoryDropdown) {
- caseCategoryDropdown.disabled = false;
- // Update placeholder text
- const placeholder =
- caseCategoryDropdown.querySelector('option[value=""]');
- if (placeholder) {
- placeholder.textContent = gettext("Select Case Category");
- }
+ // Clear dependent dropdowns
+ // Only clear dependent dropdowns when the case_type changes.
+ // This avoids removing dynamic form fields when other dropdowns (like filing_type)
+ // are changed as part of cascading operations.
+ if (fieldId === "case_type") {
+ this.clearDependentDropdowns(fieldId);
}
- }
-
- // Trigger dynamic form sections when case type changes
- if (fieldId === "case_type") {
- this.triggerDynamicFormSections();
- }
-
- // Re-render dynamic form sections when court changes (if they already exist)
- // This ensures conditional requirements are re-evaluated
- if (fieldId === "court" && this.selectedValues.case_type) {
- this.triggerDynamicFormSections();
- }
- } else if (mapping) {
- // Clear and disable the next dropdown if no value selected
- const nextDropdown = document.getElementById(mapping.next);
- if (nextDropdown) {
- this.clearDropdown(nextDropdown);
- nextDropdown.disabled = true;
- }
}
- // Clear dependent dropdowns
- // Only clear dependent dropdowns when the case_type changes.
- // This avoids removing dynamic form fields when other dropdowns (like filing_type)
- // are changed as part of cascading operations.
- if (fieldId === "case_type") {
- this.clearDependentDropdowns(fieldId);
- }
- }
-
- triggerDynamicFormSections() {
- // Check if dynamic form sections is available
- if (window.dynamicFormSections) {
- // Add a small delay to ensure the dropdown value is set
- setTimeout(() => {
- window.dynamicFormSections.handleCaseTypeChange();
- }, 100);
- } else {
- console.warn(
- "dynamicFormSections not available on window object, attempting manual trigger"
- );
-
- // Fallback: Try to trigger the case type change event manually
- setTimeout(() => {
- const caseTypeSelect = document.getElementById("case_type");
- if (caseTypeSelect && caseTypeSelect.value) {
- const changeEvent = new Event("change", { bubbles: true });
- caseTypeSelect.dispatchEvent(changeEvent);
- }
+ triggerDynamicFormSections() {
+ // Check if dynamic form sections is available
+ if (window.dynamicFormSections) {
+ // Add a small delay to ensure the dropdown value is set
+ setTimeout(() => {
+ window.dynamicFormSections.handleCaseTypeChange();
+ }, 100);
+ } else {
+ console.warn(
+ "dynamicFormSections not available on window object, attempting manual trigger"
+ );
- // Also try to find and call the dynamic form sections directly
- if (window.DynamicFormSections) {
- try {
- const dynamicSections = new window.DynamicFormSections();
- window.dynamicFormSections = dynamicSections;
+ // Fallback: Try to trigger the case type change event manually
setTimeout(() => {
- dynamicSections.handleCaseTypeChange();
+ const caseTypeSelect = document.getElementById("case_type");
+ if (caseTypeSelect && caseTypeSelect.value) {
+ const changeEvent = new Event("change", {
+ bubbles: true
+ });
+ caseTypeSelect.dispatchEvent(changeEvent);
+ }
+
+ // Also try to find and call the dynamic form sections directly
+ if (window.DynamicFormSections) {
+ try {
+ const dynamicSections = new window.DynamicFormSections();
+ window.dynamicFormSections = dynamicSections;
+ setTimeout(() => {
+ dynamicSections.handleCaseTypeChange();
+ }, 200);
+ } catch (error) {
+ console.error("Failed to create DynamicFormSections:", error);
+ }
+ }
}, 200);
- } catch (error) {
- console.error("Failed to create DynamicFormSections:", error);
- }
}
- }, 200);
- }
- }
-
- async loadFormConfiguration() {
- // Since filing types are now handled on upload page, we can load form configuration
- // when we have case type selected
- if (
- !this.selectedValues.case_category ||
- !this.selectedValues.case_type
- ) {
- return;
- }
-
- try {
- // Load optional services from Suffolk API - this is the main feature users expect
- await this.loadOptionalServices();
- } catch (error) {
- console.error("Error loading form configuration:", error);
}
- }
- async loadOptionalServices() {
- if (!this.selectedValues.court || !this.selectedValues.case_type) {
- console.warn("Missing required values for optional services");
- return;
- }
+ async loadFormConfiguration() {
+ // Since filing types are now handled on upload page, we can load form configuration
+ // when we have case type selected
+ if (
+ !this.selectedValues.case_category ||
+ !this.selectedValues.case_type
+ ) {
+ return;
+ }
- // Prevent duplicate loading
- if (this.optionalServicesLoaded) {
- return;
+ try {
+ // Load optional services from Suffolk API - this is the main feature users expect
+ await this.loadOptionalServices();
+ } catch (error) {
+ console.error("Error loading form configuration:", error);
+ }
}
- this.optionalServicesLoaded = true; // Set flag immediately to prevent race conditions
-
- try {
- const params = {
- court: this.selectedValues.court,
- case_type_id: this.selectedValues.case_type,
- jurisdiction: "illinois",
- };
-
- // Use your Django API endpoint instead of direct Suffolk API call
- const response = await this.makeRequest(
- "/api/dropdowns/optional-services/",
- params
- );
-
- if (response.success && response.data) {
- this.updateOptionalServicesFromAPI(response.data);
- } else {
- console.warn(
- "Optional services API failed:",
- response.error || "No data"
- );
- // Fall back to showing default services
- this.showDefaultOptionalServices();
- }
- } catch (error) {
- console.error("Error loading optional services:", error);
- // Fall back to showing default services
- this.showDefaultOptionalServices();
- }
- }
-
- updateOptionalServicesFromAPI(services) {
- // AGGRESSIVE CLEANUP - Remove ALL possible optional services containers
- // This includes containers with different class names and IDs that might exist
- const allPossibleSelectors = [
- "#optional-services-container",
- ".optional-services-container",
- '[data-created-by="cascading-dropdowns"]',
- ];
-
- allPossibleSelectors.forEach((selector) => {
- try {
- const containers = document.querySelectorAll(selector);
- containers.forEach((container) => {
- const isTagged =
- container.dataset &&
- container.dataset.createdBy === "cascading-dropdowns";
- const isNamed =
- container.id === "optional-services-container" ||
- container.classList.contains("optional-services-container");
- if (isTagged || isNamed) {
- container.remove();
- }
- });
- } catch (e) {
- // Ignore selector errors
- }
- });
-
- // Also look for headings but only remove their nearest safe container (one we created)
- const headings = document.querySelectorAll("h5, h4, h3");
- headings.forEach((heading) => {
- if (
- heading.textContent &&
- heading.textContent.includes("Optional Services")
- ) {
- const container = heading.closest(
- '#optional-services-container, .optional-services-container, [data-created-by="cascading-dropdowns"]'
- );
- if (container) {
- container.remove();
+ async loadOptionalServices() {
+ if (!this.selectedValues.court || !this.selectedValues.case_type) {
+ console.warn("Missing required values for optional services");
+ return;
}
- }
- });
-
- // Wait a moment for DOM cleanup
- setTimeout(() => {
- // Create a fresh container
- let servicesContainer = null;
-
- // Try to find a specific placement location first
- const preferredLocations = [
- document.querySelector("#dynamicSections"), // Our main dynamic sections container
- document.querySelector('[data-section="filing-options"]'),
- document.querySelector("#filing-options"),
- document.querySelector(".filing-options"),
- document.querySelector(".form-section:last-child"),
- document.querySelector(".form-container"),
- document.querySelector("#expertForm"), // The main form
- document.querySelector("form"),
- document.querySelector(".container"),
- document.querySelector("main"),
- document.body,
- ];
-
- let formContainer = null;
- for (const location of preferredLocations) {
- if (location) {
- formContainer = location;
- break;
+
+ // Prevent duplicate loading
+ if (this.optionalServicesLoaded) {
+ return;
}
- }
-
- if (formContainer) {
- // Reuse existing container if present to avoid duplicates
- const existing =
- document.getElementById("optional-services-container") ||
- document.querySelector('[data-created-by="cascading-dropdowns"]');
- if (existing) {
- servicesContainer = existing;
- // Clear previous content safely
- servicesContainer.innerHTML = "";
- } else {
- servicesContainer = document.createElement("div");
- servicesContainer.id = "optional-services-container";
- servicesContainer.className = "optional-services-container mt-4 mb-4";
- servicesContainer.setAttribute(
- "data-created-by",
- "cascading-dropdowns"
- );
- servicesContainer.setAttribute("data-section", "optional-services");
+
+ this.optionalServicesLoaded = true; // Set flag immediately to prevent race conditions
+
+ try {
+ const params = {
+ court: this.selectedValues.court,
+ case_type_id: this.selectedValues.case_type,
+ jurisdiction: "illinois",
+ };
+
+ // Use your Django API endpoint instead of direct Suffolk API call
+ const response = await this.makeRequest(
+ "/api/dropdowns/optional-services/",
+ params
+ );
+
+ if (response.success && response.data) {
+ this.updateOptionalServicesFromAPI(response.data);
+ } else {
+ console.warn(
+ "Optional services API failed:",
+ response.error || "No data"
+ );
+ // Fall back to showing default services
+ this.showDefaultOptionalServices();
+ }
+ } catch (error) {
+ console.error("Error loading optional services:", error);
+ // Fall back to showing default services
+ this.showDefaultOptionalServices();
}
+ }
- // Try to insert before buttons if they exist, but do it safely
- const buttons = formContainer.querySelector(
- '.form-actions, .button-group, [class*="button"], input[type="submit"], button[type="submit"]'
- );
- if (!formContainer.contains(servicesContainer)) {
- if (buttons && buttons.parentNode === formContainer) {
+ updateOptionalServicesFromAPI(services) {
+ // AGGRESSIVE CLEANUP - Remove ALL possible optional services containers
+ // This includes containers with different class names and IDs that might exist
+ const allPossibleSelectors = [
+ "#optional-services-container",
+ ".optional-services-container",
+ '[data-created-by="cascading-dropdowns"]',
+ ];
+
+ allPossibleSelectors.forEach((selector) => {
try {
- formContainer.insertBefore(servicesContainer, buttons);
- } catch (error) {
- console.warn(
- "Could not insert before buttons, appending instead:",
- error
- );
- formContainer.appendChild(servicesContainer);
- }
- } else {
- formContainer.appendChild(servicesContainer);
- }
- }
- } else {
- console.error(
- "Could not find suitable container for optional services"
- );
- return;
- }
-
- if (!services || !Array.isArray(services) || services.length === 0) {
- servicesContainer.innerHTML =
- '
No optional services available for this filing type.
';
- return;
- }
-
- // Create header
- const header = document.createElement("h3");
- header.textContent = gettext("Optional Services");
- header.className = "mb-3";
- servicesContainer.appendChild(header);
-
- // Create services list
- services.forEach((service, index) => {
- const serviceDiv = document.createElement("div");
- serviceDiv.className = "form-check mb-2";
-
- const checkbox = document.createElement("input");
- checkbox.type = "checkbox";
- checkbox.className = "form-check-input";
- checkbox.id = `service_${service.code || service.id || index}`;
- checkbox.name = "optional_services";
- checkbox.value = service.code || service.id || index;
-
- const label = document.createElement("label");
- label.className = "form-check-label";
- label.setAttribute("for", checkbox.id);
-
- // Build label text with name and fee if available
- let labelText =
- service.name || service.text || service.label || "Unknown Service";
- if (service.fee && parseFloat(service.fee) > 0) {
- labelText += ` ($${parseFloat(service.fee).toFixed(2)})`;
- }
- label.textContent = labelText;
+ const containers = document.querySelectorAll(selector);
+ containers.forEach((container) => {
+ const isTagged =
+ container.dataset &&
+ container.dataset.createdBy === "cascading-dropdowns";
+ const isNamed =
+ container.id === "optional-services-container" ||
+ container.classList.contains("optional-services-container");
+ if (isTagged || isNamed) {
+ container.remove();
+ }
+ });
+ } catch (e) {
+ // Ignore selector errors
+ }
+ });
+
+ // Also look for headings but only remove their nearest safe container (one we created)
+ const headings = document.querySelectorAll("h5, h4, h3");
+ headings.forEach((heading) => {
+ if (
+ heading.textContent &&
+ heading.textContent.includes("Optional Services")
+ ) {
+ const container = heading.closest(
+ '#optional-services-container, .optional-services-container, [data-created-by="cascading-dropdowns"]'
+ );
+ if (container) {
+ container.remove();
+ }
+ }
+ });
- serviceDiv.appendChild(checkbox);
- serviceDiv.appendChild(label);
+ // Wait a moment for DOM cleanup
+ setTimeout(() => {
+ // Create a fresh container
+ let servicesContainer = null;
+
+ // Try to find a specific placement location first
+ const preferredLocations = [
+ document.querySelector("#dynamicSections"), // Our main dynamic sections container
+ document.querySelector('[data-section="filing-options"]'),
+ document.querySelector("#filing-options"),
+ document.querySelector(".filing-options"),
+ document.querySelector(".form-section:last-child"),
+ document.querySelector(".form-container"),
+ document.querySelector("#expertForm"), // The main form
+ document.querySelector("form"),
+ document.querySelector(".container"),
+ document.querySelector("main"),
+ document.body,
+ ];
+
+ let formContainer = null;
+ for (const location of preferredLocations) {
+ if (location) {
+ formContainer = location;
+ break;
+ }
+ }
- // Add description if available
- if (service.description && service.description !== null) {
- const description = document.createElement("small");
- description.className = "form-text text-muted d-block ml-4";
- description.textContent = service.description;
- serviceDiv.appendChild(description);
- }
+ if (formContainer) {
+ // Reuse existing container if present to avoid duplicates
+ const existing =
+ document.getElementById("optional-services-container") ||
+ document.querySelector('[data-created-by="cascading-dropdowns"]');
+ if (existing) {
+ servicesContainer = existing;
+ // Clear previous content safely
+ servicesContainer.innerHTML = "";
+ } else {
+ servicesContainer = document.createElement("div");
+ servicesContainer.id = "optional-services-container";
+ servicesContainer.className = "optional-services-container mt-4 mb-4";
+ servicesContainer.setAttribute(
+ "data-created-by",
+ "cascading-dropdowns"
+ );
+ servicesContainer.setAttribute("data-section", "optional-services");
+ }
- servicesContainer.appendChild(serviceDiv);
- });
-
- // Make sure the container is visible
- servicesContainer.style.display = "block";
- }, 100); // Small delay to ensure cleanup completes
- }
-
- showDefaultOptionalServices() {
- // Show basic optional services if API fails
-
- let servicesContainer =
- document.querySelector(".optional-services-container") ||
- document.querySelector("#optional-services-container") ||
- document.querySelector(".services-container");
-
- if (!servicesContainer) {
- // Try to create one
- const formContainer =
- document.querySelector(".form-container") ||
- document.querySelector("#filing-form") ||
- document.querySelector("form") ||
- document.querySelector(".container") ||
- document.querySelector("main") ||
- document.body;
-
- if (formContainer) {
- servicesContainer = document.createElement("div");
- servicesContainer.id = "optional-services-container";
- servicesContainer.className = "optional-services-container mt-4";
- formContainer.appendChild(servicesContainer);
- }
- }
+ // Try to insert before buttons if they exist, but do it safely
+ const buttons = formContainer.querySelector(
+ '.form-actions, .button-group, [class*="button"], input[type="submit"], button[type="submit"]'
+ );
+ if (!formContainer.contains(servicesContainer)) {
+ if (buttons && buttons.parentNode === formContainer) {
+ try {
+ formContainer.insertBefore(servicesContainer, buttons);
+ } catch (error) {
+ console.warn(
+ "Could not insert before buttons, appending instead:",
+ error
+ );
+ formContainer.appendChild(servicesContainer);
+ }
+ } else {
+ formContainer.appendChild(servicesContainer);
+ }
+ }
+ } else {
+ console.error(
+ "Could not find suitable container for optional services"
+ );
+ return;
+ }
+
+ if (!services || !Array.isArray(services) || services.length === 0) {
+ servicesContainer.innerHTML =
+ 'No optional services available for this filing type.
';
+ return;
+ }
+
+ // Create header
+ const header = document.createElement("h3");
+ header.textContent = gettext("Optional Services");
+ header.className = "mb-3";
+ servicesContainer.appendChild(header);
+
+ // Create services list
+ services.forEach((service, index) => {
+ const serviceDiv = document.createElement("div");
+ serviceDiv.className = "form-check mb-2";
+
+ const checkbox = document.createElement("input");
+ checkbox.type = "checkbox";
+ checkbox.className = "form-check-input";
+ checkbox.id = `service_${service.code || service.id || index}`;
+ checkbox.name = "optional_services";
+ checkbox.value = service.code || service.id || index;
+
+ const label = document.createElement("label");
+ label.className = "form-check-label";
+ label.setAttribute("for", checkbox.id);
+
+ // Build label text with name and fee if available
+ let labelText =
+ service.name || service.text || service.label || "Unknown Service";
+ if (service.fee && parseFloat(service.fee) > 0) {
+ labelText += ` ($${parseFloat(service.fee).toFixed(2)})`;
+ }
+ label.textContent = labelText;
- if (!servicesContainer) {
- console.error(
- "Could not find or create container for default optional services"
- );
- return;
+ serviceDiv.appendChild(checkbox);
+ serviceDiv.appendChild(label);
+
+ // Add description if available
+ if (service.description && service.description !== null) {
+ const description = document.createElement("small");
+ description.className = "form-text text-muted d-block ml-4";
+ description.textContent = service.description;
+ serviceDiv.appendChild(description);
+ }
+
+ servicesContainer.appendChild(serviceDiv);
+ });
+
+ // Make sure the container is visible
+ servicesContainer.style.display = "block";
+ }, 100); // Small delay to ensure cleanup completes
}
- servicesContainer.innerHTML = `
+ showDefaultOptionalServices() {
+ // Show basic optional services if API fails
+
+ let servicesContainer =
+ document.querySelector(".optional-services-container") ||
+ document.querySelector("#optional-services-container") ||
+ document.querySelector(".services-container");
+
+ if (!servicesContainer) {
+ // Try to create one
+ const formContainer =
+ document.querySelector(".form-container") ||
+ document.querySelector("#filing-form") ||
+ document.querySelector("form") ||
+ document.querySelector(".container") ||
+ document.querySelector("main") ||
+ document.body;
+
+ if (formContainer) {
+ servicesContainer = document.createElement("div");
+ servicesContainer.id = "optional-services-container";
+ servicesContainer.className = "optional-services-container mt-4";
+ formContainer.appendChild(servicesContainer);
+ }
+ }
+
+ if (!servicesContainer) {
+ console.error(
+ "Could not find or create container for default optional services"
+ );
+ return;
+ }
+
+ servicesContainer.innerHTML = `
Optional Services
@@ -637,374 +641,378 @@ class CascadingDropdowns {
`;
- servicesContainer.style.display = "block";
- }
+ servicesContainer.style.display = "block";
+ }
- validateParameters(fieldId, params) {
- // Define required parameters for each field
- const requiredParams = {
- court: ["jurisdiction"],
- case_category: ["jurisdiction", "court"],
- case_type: ["jurisdiction", "parent"], // parent = category_id
- };
+ validateParameters(fieldId, params) {
+ // Define required parameters for each field
+ const requiredParams = {
+ court: ["jurisdiction"],
+ case_category: ["jurisdiction", "court"],
+ case_type: ["jurisdiction", "parent"], // parent = category_id
+ };
+
+ const required = requiredParams[fieldId] || [];
+ const missing = required.filter((param) => !params[param]);
- const required = requiredParams[fieldId] || [];
- const missing = required.filter((param) => !params[param]);
+ if (missing.length > 0) {
+ console.error(`Missing required parameters for ${fieldId}:`, missing);
+ return false;
+ }
- if (missing.length > 0) {
- console.error(`Missing required parameters for ${fieldId}:`, missing);
- return false;
+ return true;
}
- return true;
- }
-
- clearDependentDropdowns(changedFieldId) {
- const dependencies = {
- court: ["case_category", "case_type"],
- case_category: ["case_type"],
- case_type: ["party_type"], // case_type is now the final dropdown on expert form
- };
-
- const toClear = dependencies[changedFieldId] || [];
- toClear.forEach((fieldId) => {
- const dropdown = document.getElementById(fieldId);
- if (dropdown && fieldId !== this.dropdownMapping[changedFieldId]?.next) {
- // Special handling for case_type - notify dynamic forms and preserve temporarily
- if (fieldId === "case_type") {
- // Store the current value before clearing
- const currentCaseTypeValue = dropdown.value;
- const currentCaseTypeText =
- dropdown.options[dropdown.selectedIndex]?.text || "";
-
- // Notify dynamic forms that case_type is being cleared programmatically
- if (window.dynamicFormSections) {
- window.dynamicFormSections.currentCaseType = null;
- // Don't hide sections immediately - let the restoration process handle it
- }
-
- // Clear the dropdown but keep the dynamic forms intact temporarily
- this.clearDropdown(dropdown);
- dropdown.disabled = true;
-
- // If this was triggered by filing_type change and we have a case type,
- // try to restore it after the dropdown gets repopulated
- if (changedFieldId === "filing_type" && currentCaseTypeValue) {
- setTimeout(() => {
- // Check if dropdown got repopulated
- if (dropdown.options.length > 1) {
- const matchingOption = dropdown.querySelector(
- `option[value="${currentCaseTypeValue}"]`
- );
- if (matchingOption) {
- dropdown.value = currentCaseTypeValue;
- dropdown.disabled = false;
- this.selectedValues.case_type = currentCaseTypeValue;
+ clearDependentDropdowns(changedFieldId) {
+ const dependencies = {
+ court: ["case_category", "case_type"],
+ case_category: ["case_type"],
+ case_type: ["party_type"], // case_type is now the final dropdown on expert form
+ };
- // Re-trigger dynamic form sections
- setTimeout(() => {
+ const toClear = dependencies[changedFieldId] || [];
+ toClear.forEach((fieldId) => {
+ const dropdown = document.getElementById(fieldId);
+ if (dropdown && fieldId !== this.dropdownMapping[changedFieldId]?.next) {
+ // Special handling for case_type - notify dynamic forms and preserve temporarily
+ if (fieldId === "case_type") {
+ // Store the current value before clearing
+ const currentCaseTypeValue = dropdown.value;
+ const currentCaseTypeText =
+ dropdown.options[dropdown.selectedIndex]?.text || "";
+
+ // Notify dynamic forms that case_type is being cleared programmatically
if (window.dynamicFormSections) {
- window.dynamicFormSections.handleCaseTypeChange();
+ window.dynamicFormSections.currentCaseType = null;
+ // Don't hide sections immediately - let the restoration process handle it
+ }
+
+ // Clear the dropdown but keep the dynamic forms intact temporarily
+ this.clearDropdown(dropdown);
+ dropdown.disabled = true;
+
+ // If this was triggered by filing_type change and we have a case type,
+ // try to restore it after the dropdown gets repopulated
+ if (changedFieldId === "filing_type" && currentCaseTypeValue) {
+ setTimeout(() => {
+ // Check if dropdown got repopulated
+ if (dropdown.options.length > 1) {
+ const matchingOption = dropdown.querySelector(
+ `option[value="${currentCaseTypeValue}"]`
+ );
+ if (matchingOption) {
+ dropdown.value = currentCaseTypeValue;
+ dropdown.disabled = false;
+ this.selectedValues.case_type = currentCaseTypeValue;
+
+ // Re-trigger dynamic form sections
+ setTimeout(() => {
+ if (window.dynamicFormSections) {
+ window.dynamicFormSections.handleCaseTypeChange();
+ }
+ }, 100);
+ }
+ }
+ }, 1500); // Give more time for dropdown to repopulate
}
- }, 100);
+ } else {
+ // Regular clearing for other dropdowns
+ this.clearDropdown(dropdown);
+ dropdown.disabled = true;
}
- }
- }, 1500); // Give more time for dropdown to repopulate
- }
- } else {
- // Regular clearing for other dropdowns
- this.clearDropdown(dropdown);
- dropdown.disabled = true;
- }
- }
- });
- }
-
- populateDropdown(dropdown, options) {
- // Special handling for filing type search dropdown
- if (dropdown.id === 'filing_type' && window.filingTypeSearch) {
- window.filingTypeSearch.updateOptions(options);
- window.filingTypeSearch.enable();
- return;
- }
-
- const placeholder = dropdown.querySelector('option[value=""]').textContent;
-
- // Clear dropdown and remove any visual selection indicators
- dropdown.innerHTML = `${placeholder} `;
- dropdown.classList.remove("has-selection", "selected", "success");
- dropdown.removeAttribute("data-selected");
- dropdown.value = "";
-
- // Check if options is valid
- if (!options || !Array.isArray(options)) {
- console.warn("Invalid options data:", options);
- this.showError(dropdown, "No options available");
- return;
+ }
+ });
}
- let recommendedOption = null;
-
- options.forEach((option) => {
- const optionElement = document.createElement("option");
- optionElement.value = option.value || option.id;
- optionElement.textContent = option.label || option.name || option.text;
-
- // Check if this is a recommended option
- if (
- option.recommended ||
- option.selected ||
- option.default
- ) {
- optionElement.style.fontWeight = "bold";
- recommendedOption = option.value || option.id;
- }
-
- dropdown.appendChild(optionElement);
- });
-
- // Auto-select recommended court and trigger change event
- if ((dropdown.id === "court" || dropdown.id === "case_category" || dropdown.id === "case_type") && recommendedOption) {
- this.isAutomaticSelection = true; // Mark as automatic selection
- dropdown.value = recommendedOption;
- if (dropdown.id === "court") {
- this.selectedValues.court = recommendedOption;
- }
-
- // Show a brief notification about the auto-selection
- this.showRecommendationNotice(dropdown, dropdown.id);
-
- // Trigger change event to load dependent dropdowns
- setTimeout(() => {
- dropdown.dispatchEvent(new Event("change", { bubbles: true }));
- }, 500);
- }
+ populateDropdown(dropdown, options) {
+ // Special handling for filing type search dropdown
+ if (dropdown.id === 'filing_type' && window.filingTypeSearch) {
+ window.filingTypeSearch.updateOptions(options);
+ window.filingTypeSearch.enable();
+ return;
+ }
- // Handle user's preferred county auto-selection (fallback)
- if (
- dropdown.id === "court" &&
- !recommendedOption &&
- this.userProfile &&
- this.userProfile.preferred_county
- ) {
- this.isAutomaticSelection = true; // Mark as automatic selection
- const preferredValue = this.userProfile.preferred_county;
- const preferredOption = dropdown.querySelector(
- `option[value="${preferredValue}"]`
- );
- if (preferredOption) {
- dropdown.value = preferredValue;
- this.selectedValues.court = preferredValue;
-
- // Show notice for this selection too
- this.showRecommendationNotice(dropdown, "court");
-
- // Trigger change event to load dependent dropdowns
- setTimeout(() => {
- dropdown.dispatchEvent(new Event("change", { bubbles: true }));
- }, 500);
- }
- }
- }
+ const placeholder = dropdown.querySelector('option[value=""]').textContent;
+
+ // Clear dropdown and remove any visual selection indicators
+ dropdown.innerHTML = `${placeholder} `;
+ dropdown.classList.remove("has-selection", "selected", "success");
+ dropdown.removeAttribute("data-selected");
+ dropdown.value = "";
+
+ // Check if options is valid
+ if (!options || !Array.isArray(options)) {
+ console.warn("Invalid options data:", options);
+ this.showError(dropdown, "No options available");
+ return;
+ }
- showRecommendationNotice(dropdown, type) {
- // Remove any existing recommendation notice for this dropdown first
- const existingNotice = dropdown.parentNode.querySelector('.recommendation-notice');
- if (existingNotice) {
- existingNotice.remove();
+ let recommendedOption = null;
+
+ options.forEach((option) => {
+ const optionElement = document.createElement("option");
+ optionElement.value = option.value || option.id;
+ optionElement.textContent = option.label || option.name || option.text;
+
+ // Check if this is a recommended option
+ if (
+ option.recommended ||
+ option.selected ||
+ option.default
+ ) {
+ optionElement.style.fontWeight = "bold";
+ recommendedOption = option.value || option.id;
+ }
+
+ dropdown.appendChild(optionElement);
+ });
+
+ // Auto-select recommended court and trigger change event
+ if ((dropdown.id === "court" || dropdown.id === "case_category" || dropdown.id === "case_type") && recommendedOption) {
+ this.isAutomaticSelection = true; // Mark as automatic selection
+ dropdown.value = recommendedOption;
+ if (dropdown.id === "court") {
+ this.selectedValues.court = recommendedOption;
+ }
+
+ // Show a brief notification about the auto-selection
+ this.showRecommendationNotice(dropdown, dropdown.id);
+
+ // Trigger change event to load dependent dropdowns
+ setTimeout(() => {
+ dropdown.dispatchEvent(new Event("change", {
+ bubbles: true
+ }));
+ }, 500);
+ }
+
+ // Handle user's preferred county auto-selection (fallback)
+ if (
+ dropdown.id === "court" &&
+ !recommendedOption &&
+ this.userProfile &&
+ this.userProfile.preferred_county
+ ) {
+ this.isAutomaticSelection = true; // Mark as automatic selection
+ const preferredValue = this.userProfile.preferred_county;
+ const preferredOption = dropdown.querySelector(
+ `option[value="${preferredValue}"]`
+ );
+ if (preferredOption) {
+ dropdown.value = preferredValue;
+ this.selectedValues.court = preferredValue;
+
+ // Show notice for this selection too
+ this.showRecommendationNotice(dropdown, "court");
+
+ // Trigger change event to load dependent dropdowns
+ setTimeout(() => {
+ dropdown.dispatchEvent(new Event("change", {
+ bubbles: true
+ }));
+ }, 500);
+ }
+ }
}
- if (type === "court") {
- // Create a persistent notice to show the user why this option was selected
- const notice = document.createElement("div");
- notice.className = "alert alert-success recommendation-notice";
- notice.style.cssText =
- "position: relative; z-index: 1000; margin-top: 5px; margin-bottom: 10px; padding: 8px 12px; font-size: 0.875rem; border-radius: 4px;";
- notice.innerHTML = ` We've pre-selected some choices based on your uploaded form.`;
- // Find the label for this dropdown to insert the notice above it
- const dropdownLabel = dropdown.parentNode.querySelector(`label[for="${dropdown.id}"]`);
-
- if (dropdownLabel) {
- // Insert the notice before the label (above the title)
- dropdownLabel.parentNode.insertBefore(notice, dropdownLabel);
- } else {
- // Fallback: insert before the dropdown if no label is found
- dropdown.parentNode.insertBefore(notice, dropdown);
- }
+ showRecommendationNotice(dropdown, type) {
+ // Remove any existing recommendation notice for this dropdown first
+ const existingNotice = dropdown.parentNode.querySelector('.recommendation-notice');
+ if (existingNotice) {
+ existingNotice.remove();
+ }
+
+ if (type === "court") {
+ // Create a persistent notice to show the user why this option was selected
+ const notice = document.createElement("div");
+ notice.className = "alert alert-success recommendation-notice";
+ notice.style.cssText =
+ "position: relative; z-index: 1000; margin-top: 5px; margin-bottom: 10px; padding: 8px 12px; font-size: 0.875rem; border-radius: 4px;";
+ notice.innerHTML = ` We've pre-selected some choices based on your uploaded form.`;
+ // Find the label for this dropdown to insert the notice above it
+ const dropdownLabel = dropdown.parentNode.querySelector(`label[for="${dropdown.id}"]`);
+
+ if (dropdownLabel) {
+ // Insert the notice before the label (above the title)
+ dropdownLabel.parentNode.insertBefore(notice, dropdownLabel);
+ } else {
+ // Fallback: insert before the dropdown if no label is found
+ dropdown.parentNode.insertBefore(notice, dropdown);
+ }
+ }
+
+ // The notice will now persist until the court dropdown changes or page reloads
+ // No automatic removal timeout
}
- // The notice will now persist until the court dropdown changes or page reloads
- // No automatic removal timeout
- }
-
- clearAllRecommendationNotices() {
- // Remove all existing recommendation notices (green success alerts)
- const existingNotices = document.querySelectorAll(
- ".recommendation-notice, .alert.alert-success.recommendation-notice"
- );
- existingNotices.forEach((notice) => {
- if (notice.parentNode) {
- notice.parentNode.removeChild(notice);
- }
- });
- }
-
- clearAllDropdownVisualIndicators() {
- // Clear visual indicators from all dropdowns
- const allDropdowns = document.querySelectorAll(
- ".dropdown-field, select.form-select"
- );
- allDropdowns.forEach((dropdown) => {
- // Remove success/selection classes
- dropdown.classList.remove(
- "has-selection",
- "selected",
- "success",
- "is-valid"
- );
- dropdown.removeAttribute("data-selected");
-
- // Remove any checkmark or success icons that might be added via pseudo-elements
- const parent = dropdown.parentElement;
- if (parent) {
- parent.classList.remove(
- "has-success",
- "field-success",
- "validation-success"
+ clearAllRecommendationNotices() {
+ // Remove all existing recommendation notices (green success alerts)
+ const existingNotices = document.querySelectorAll(
+ ".recommendation-notice, .alert.alert-success.recommendation-notice"
);
+ existingNotices.forEach((notice) => {
+ if (notice.parentNode) {
+ notice.parentNode.removeChild(notice);
+ }
+ });
+ }
- // Remove any success icons that might have been added
- const successIcons = parent.querySelectorAll(
- ".fa-check, .fa-check-circle, .success-icon"
+ clearAllDropdownVisualIndicators() {
+ // Clear visual indicators from all dropdowns
+ const allDropdowns = document.querySelectorAll(
+ ".dropdown-field, select.form-select"
);
- successIcons.forEach((icon) => icon.remove());
- }
- });
- }
-
- resetDependentDropdowns(changedFieldId) {
- // Define the hierarchy of dependent dropdowns
- const hierarchy = [
- "court",
- "case_category",
- "case_type",
- "party_type",
- "filing_type",
- "document_type",
- ];
-
- // Find the index of the changed field in the hierarchy
- const changedIndex = hierarchy.indexOf(changedFieldId);
-
- if (changedIndex === -1) return; // Field not in hierarchy
-
- // Reset all dropdowns that come after the changed field in the hierarchy
- for (let i = changedIndex + 1; i < hierarchy.length; i++) {
- const fieldToReset = hierarchy[i];
- const dropdown = document.getElementById(fieldToReset);
-
- if (dropdown) {
- // Clear the dropdown
- this.clearDropdown(dropdown);
-
- // Reset the stored value
- this.selectedValues[fieldToReset] = null;
- }
- }
+ allDropdowns.forEach((dropdown) => {
+ // Remove success/selection classes
+ dropdown.classList.remove(
+ "has-selection",
+ "selected",
+ "success",
+ "is-valid"
+ );
+ dropdown.removeAttribute("data-selected");
+
+ // Remove any checkmark or success icons that might be added via pseudo-elements
+ const parent = dropdown.parentElement;
+ if (parent) {
+ parent.classList.remove(
+ "has-success",
+ "field-success",
+ "validation-success"
+ );
- // Also clear dynamic form sections when case_type is reset
- if (changedIndex <= hierarchy.indexOf("case_type")) {
- const dynamicSectionsContainer =
- document.getElementById("dynamic-sections");
- if (dynamicSectionsContainer) {
- dynamicSectionsContainer.innerHTML = "";
- }
- }
- }
-
- clearDropdown(dropdown) {
- // Special handling for filing type search dropdown
- if (dropdown.id === 'filing_type' && window.filingTypeSearch) {
- window.filingTypeSearch.reset();
- window.filingTypeSearch.disable();
- return;
+ // Remove any success icons that might have been added
+ const successIcons = parent.querySelectorAll(
+ ".fa-check, .fa-check-circle, .success-icon"
+ );
+ successIcons.forEach((icon) => icon.remove());
+ }
+ });
}
-
- let placeholder =
- dropdown.querySelector('option[value=""]')?.textContent ||
- "Please select...";
-
- // Special handling for case category when no court is selected
- if (dropdown.id === "case_category" && !this.selectedValues.court) {
- placeholder = "First select a court";
- dropdown.disabled = true;
- } else if (dropdown.id === "case_category" && this.selectedValues.court) {
- placeholder = "Select Case Category";
- dropdown.disabled = false;
+
+ resetDependentDropdowns(changedFieldId) {
+ // Define the hierarchy of dependent dropdowns
+ const hierarchy = [
+ "court",
+ "case_category",
+ "case_type",
+ "party_type",
+ "filing_type",
+ "document_type",
+ ];
+
+ // Find the index of the changed field in the hierarchy
+ const changedIndex = hierarchy.indexOf(changedFieldId);
+
+ if (changedIndex === -1) return; // Field not in hierarchy
+
+ // Reset all dropdowns that come after the changed field in the hierarchy
+ for (let i = changedIndex + 1; i < hierarchy.length; i++) {
+ const fieldToReset = hierarchy[i];
+ const dropdown = document.getElementById(fieldToReset);
+
+ if (dropdown) {
+ // Clear the dropdown
+ this.clearDropdown(dropdown);
+
+ // Reset the stored value
+ this.selectedValues[fieldToReset] = null;
+ }
+ }
+
+ // Also clear dynamic form sections when case_type is reset
+ if (changedIndex <= hierarchy.indexOf("case_type")) {
+ const dynamicSectionsContainer =
+ document.getElementById("dynamic-sections");
+ if (dynamicSectionsContainer) {
+ dynamicSectionsContainer.innerHTML = "";
+ }
+ }
}
- // Clear all options and reset to placeholder
- dropdown.innerHTML = `${placeholder} `;
+ clearDropdown(dropdown) {
+ // Special handling for filing type search dropdown
+ if (dropdown.id === 'filing_type' && window.filingTypeSearch) {
+ window.filingTypeSearch.reset();
+ window.filingTypeSearch.disable();
+ return;
+ }
- // Remove any visual indicators or classes that might show selection state
- dropdown.classList.remove("has-selection", "selected", "success");
- dropdown.removeAttribute("data-selected");
+ let placeholder =
+ dropdown.querySelector('option[value=""]')?.textContent ||
+ "Please select...";
+
+ // Special handling for case category when no court is selected
+ if (dropdown.id === "case_category" && !this.selectedValues.court) {
+ placeholder = "First select a court";
+ dropdown.disabled = true;
+ } else if (dropdown.id === "case_category" && this.selectedValues.court) {
+ placeholder = "Select Case Category";
+ dropdown.disabled = false;
+ }
- // Reset dropdown value explicitly to ensure no selection state
- dropdown.value = "";
- }
+ // Clear all options and reset to placeholder
+ dropdown.innerHTML = `${placeholder} `;
- showLoader(loader) {
- if (loader) loader.style.display = "block";
- }
+ // Remove any visual indicators or classes that might show selection state
+ dropdown.classList.remove("has-selection", "selected", "success");
+ dropdown.removeAttribute("data-selected");
- hideLoader(loader) {
- if (loader) loader.style.display = "none";
- }
+ // Reset dropdown value explicitly to ensure no selection state
+ dropdown.value = "";
+ }
- showError(dropdown, message) {
- dropdown.innerHTML = `Error: ${message} `;
- dropdown.disabled = false;
- }
+ showLoader(loader) {
+ if (loader) loader.style.display = "block";
+ }
- enableDropdown(fieldId) {
- const dropdown = document.getElementById(fieldId);
- if (dropdown) {
- dropdown.disabled = false;
+ hideLoader(loader) {
+ if (loader) loader.style.display = "none";
}
- }
-
- /**
- * Clear all dropdowns and reset their state
- */
- clearAllDropdowns() {
- const dropdownFields = ['court', 'case_category', 'case_type', 'filing_type', 'document_type'];
-
- dropdownFields.forEach(fieldId => {
- const dropdown = document.getElementById(fieldId);
- if (dropdown) {
- this.clearDropdown(dropdown);
- if (fieldId !== 'court') {
- dropdown.disabled = true;
+
+ showError(dropdown, message) {
+ dropdown.innerHTML = `Error: ${message} `;
+ dropdown.disabled = false;
+ }
+
+ enableDropdown(fieldId) {
+ const dropdown = document.getElementById(fieldId);
+ if (dropdown) {
+ dropdown.disabled = false;
+ }
+ }
+
+ /**
+ * Clear all dropdowns and reset their state
+ */
+ clearAllDropdowns() {
+ const dropdownFields = ['court', 'case_category', 'case_type', 'filing_type', 'document_type'];
+
+ dropdownFields.forEach(fieldId => {
+ const dropdown = document.getElementById(fieldId);
+ if (dropdown) {
+ this.clearDropdown(dropdown);
+ if (fieldId !== 'court') {
+ dropdown.disabled = true;
+ }
+ }
+
+ // Reset selected values
+ this.selectedValues[fieldId] = null;
+ });
+
+ // Hide dynamic sections
+ const dynamicSections = document.getElementById('dynamicSections');
+ if (dynamicSections) {
+ dynamicSections.style.display = 'none';
+ dynamicSections.innerHTML = '';
}
- }
-
- // Reset selected values
- this.selectedValues[fieldId] = null;
- });
-
- // Hide dynamic sections
- const dynamicSections = document.getElementById('dynamicSections');
- if (dynamicSections) {
- dynamicSections.style.display = 'none';
- dynamicSections.innerHTML = '';
}
- }
}
// Export for module use or make globally available
if (typeof module !== "undefined" && module.exports) {
- module.exports = CascadingDropdowns;
+ module.exports = CascadingDropdowns;
} else {
- window.CascadingDropdowns = CascadingDropdowns;
-}
+ window.CascadingDropdowns = CascadingDropdowns;
+}
\ No newline at end of file
diff --git a/efile_app/efile/static/js/components/search-dropdown.js b/efile_app/efile/static/js/components/search-dropdown.js
index daf03de..dcfc31b 100644
--- a/efile_app/efile/static/js/components/search-dropdown.js
+++ b/efile_app/efile/static/js/components/search-dropdown.js
@@ -11,325 +11,329 @@
* - Accessibility features
*/
class SearchDropdown {
- constructor(fieldId, options = {}) {
- this.fieldId = fieldId;
- this.options = {
- placeholder: 'Search...',
- noResultsText: 'No matching options found',
- ...options
- };
-
- // Get DOM elements
- this.input = document.getElementById(`${fieldId}_search`);
- this.select = document.getElementById(fieldId);
- this.results = document.getElementById(`${fieldId}-results`);
- this.selected = document.getElementById(`${fieldId}-selected`);
- this.container = document.getElementById(`${fieldId}-container`);
-
- // State
- this.allOptions = [];
- this.filteredOptions = [];
- this.highlightedIndex = -1;
- this.isInitialized = false;
- this.isUpdatingSelect = false; // Flag to prevent infinite loops
-
- this.init();
- }
-
- init() {
- if (!this.input || !this.select || !this.results || !this.selected) {
- console.error(`SearchDropdown: Required elements not found for field ${this.fieldId}`);
- return;
+ constructor(fieldId, options = {}) {
+ this.fieldId = fieldId;
+ this.options = {
+ placeholder: 'Search...',
+ noResultsText: 'No matching options found',
+ ...options
+ };
+
+ // Get DOM elements
+ this.input = document.getElementById(`${fieldId}_search`);
+ this.select = document.getElementById(fieldId);
+ this.results = document.getElementById(`${fieldId}-results`);
+ this.selected = document.getElementById(`${fieldId}-selected`);
+ this.container = document.getElementById(`${fieldId}-container`);
+
+ // State
+ this.allOptions = [];
+ this.filteredOptions = [];
+ this.highlightedIndex = -1;
+ this.isInitialized = false;
+ this.isUpdatingSelect = false; // Flag to prevent infinite loops
+
+ this.init();
}
-
- this.setupEventListeners();
- this.isInitialized = true;
- }
-
- setupEventListeners() {
- // Input events
- this.input.addEventListener('input', (e) => this.handleInput(e));
- this.input.addEventListener('focus', (e) => this.handleFocus(e));
- this.input.addEventListener('blur', (e) => this.handleBlur(e));
- this.input.addEventListener('keydown', (e) => this.handleKeydown(e));
-
- // Clear button
- const clearBtn = this.selected.querySelector('.btn-clear');
- if (clearBtn) {
- clearBtn.addEventListener('click', () => this.clearSelection());
+
+ init() {
+ if (!this.input || !this.select || !this.results || !this.selected) {
+ console.error(`SearchDropdown: Required elements not found for field ${this.fieldId}`);
+ return;
+ }
+
+ this.setupEventListeners();
+ this.isInitialized = true;
}
-
- // Hidden select changes (for external updates)
- this.select.addEventListener('change', () => this.syncFromSelect());
-
- // Click outside to close
- document.addEventListener('click', (e) => {
- if (this.container && !this.container.contains(e.target)) {
- this.hideResults();
- }
- });
- }
-
- handleInput(e) {
- const query = e.target.value.toLowerCase();
- this.filterOptions(query);
- this.showResults();
- this.highlightedIndex = -1;
- }
-
- handleFocus(e) {
- if (this.allOptions.length > 0) {
- this.filterOptions(e.target.value.toLowerCase());
- this.showResults();
+
+ setupEventListeners() {
+ // Input events
+ this.input.addEventListener('input', (e) => this.handleInput(e));
+ this.input.addEventListener('focus', (e) => this.handleFocus(e));
+ this.input.addEventListener('blur', (e) => this.handleBlur(e));
+ this.input.addEventListener('keydown', (e) => this.handleKeydown(e));
+
+ // Clear button
+ const clearBtn = this.selected.querySelector('.btn-clear');
+ if (clearBtn) {
+ clearBtn.addEventListener('click', () => this.clearSelection());
+ }
+
+ // Hidden select changes (for external updates)
+ this.select.addEventListener('change', () => this.syncFromSelect());
+
+ // Click outside to close
+ document.addEventListener('click', (e) => {
+ if (this.container && !this.container.contains(e.target)) {
+ this.hideResults();
+ }
+ });
}
- }
-
- handleBlur(e) {
- // Delay hiding to allow clicks on results
- setTimeout(() => {
- if (this.container && !this.container.contains(document.activeElement)) {
- this.hideResults();
- }
- }, 150);
- }
-
- handleKeydown(e) {
- if (!this.isResultsVisible()) return;
-
- switch (e.key) {
- case 'ArrowDown':
- e.preventDefault();
- this.highlightNext();
- break;
- case 'ArrowUp':
- e.preventDefault();
- this.highlightPrevious();
- break;
- case 'Enter':
- e.preventDefault();
- this.selectHighlighted();
- break;
- case 'Escape':
- e.preventDefault();
- this.hideResults();
- break;
+
+ handleInput(e) {
+ const query = e.target.value.toLowerCase();
+ this.filterOptions(query);
+ this.showResults();
+ this.highlightedIndex = -1;
+ }
+
+ handleFocus(e) {
+ if (this.allOptions.length > 0) {
+ this.filterOptions(e.target.value.toLowerCase());
+ this.showResults();
+ }
+ }
+
+ handleBlur(e) {
+ // Delay hiding to allow clicks on results
+ setTimeout(() => {
+ if (this.container && !this.container.contains(document.activeElement)) {
+ this.hideResults();
+ }
+ }, 150);
+ }
+
+ handleKeydown(e) {
+ if (!this.isResultsVisible()) return;
+
+ switch (e.key) {
+ case 'ArrowDown':
+ e.preventDefault();
+ this.highlightNext();
+ break;
+ case 'ArrowUp':
+ e.preventDefault();
+ this.highlightPrevious();
+ break;
+ case 'Enter':
+ e.preventDefault();
+ this.selectHighlighted();
+ break;
+ case 'Escape':
+ e.preventDefault();
+ this.hideResults();
+ break;
+ }
+ }
+
+ updateOptions(options) {
+ this.allOptions = options.map(option => ({
+ value: option.value,
+ text: option.text,
+ searchText: option.text.toLowerCase()
+ }));
+
+ // Update the hidden select as well
+ this.select.innerHTML = `Select ${this.getFieldLabel()} `;
+ options.forEach(option => {
+ const optionElement = document.createElement('option');
+ optionElement.value = option.value;
+ optionElement.textContent = option.text;
+ this.select.appendChild(optionElement);
+ });
+
+ this.filterOptions();
}
- }
-
- updateOptions(options) {
- this.allOptions = options.map(option => ({
- value: option.value,
- text: option.text,
- searchText: option.text.toLowerCase()
- }));
-
- // Update the hidden select as well
- this.select.innerHTML = `Select ${this.getFieldLabel()} `;
- options.forEach(option => {
- const optionElement = document.createElement('option');
- optionElement.value = option.value;
- optionElement.textContent = option.text;
- this.select.appendChild(optionElement);
- });
-
- this.filterOptions();
- }
-
- filterOptions(query = '') {
- if (query === '') {
- this.filteredOptions = [...this.allOptions];
- } else {
- this.filteredOptions = this.allOptions.filter(option =>
- option.searchText.includes(query)
- );
+
+ filterOptions(query = '') {
+ if (query === '') {
+ this.filteredOptions = [...this.allOptions];
+ } else {
+ this.filteredOptions = this.allOptions.filter(option =>
+ option.searchText.includes(query)
+ );
+ }
+ this.renderResults();
}
- this.renderResults();
- }
-
- renderResults() {
- if (this.filteredOptions.length === 0) {
- this.results.innerHTML = `${this.options.noResultsText}
`;
- } else {
- this.results.innerHTML = this.filteredOptions
- .map((option, index) => `
+
+ renderResults() {
+ if (this.filteredOptions.length === 0) {
+ this.results.innerHTML = `${this.options.noResultsText}
`;
+ } else {
+ this.results.innerHTML = this.filteredOptions
+ .map((option, index) => `
${this.highlightMatch(option.text, this.input.value)}
`).join('');
-
- // Add event listeners to result items
- this.results.querySelectorAll('.search-dropdown-item').forEach((item, index) => {
- item.addEventListener('mousedown', (e) => {
- e.preventDefault(); // Prevent blur
- this.selectOption(this.filteredOptions[index]);
- });
-
- item.addEventListener('mouseenter', () => {
- this.highlightedIndex = index;
- this.updateHighlight();
- });
- });
+
+ // Add event listeners to result items
+ this.results.querySelectorAll('.search-dropdown-item').forEach((item, index) => {
+ item.addEventListener('mousedown', (e) => {
+ e.preventDefault(); // Prevent blur
+ this.selectOption(this.filteredOptions[index]);
+ });
+
+ item.addEventListener('mouseenter', () => {
+ this.highlightedIndex = index;
+ this.updateHighlight();
+ });
+ });
+ }
}
- }
-
- highlightMatch(text, query) {
- if (!query) return text;
-
- const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
- return text.replace(regex, '$1 ');
- }
-
- highlightNext() {
- this.highlightedIndex = Math.min(this.highlightedIndex + 1, this.filteredOptions.length - 1);
- this.updateHighlight();
- }
-
- highlightPrevious() {
- this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
- this.updateHighlight();
- }
-
- updateHighlight() {
- this.results.querySelectorAll('.search-dropdown-item').forEach((item, index) => {
- item.classList.toggle('highlighted', index === this.highlightedIndex);
- });
- }
-
- selectHighlighted() {
- if (this.highlightedIndex >= 0 && this.highlightedIndex < this.filteredOptions.length) {
- this.selectOption(this.filteredOptions[this.highlightedIndex]);
+
+ highlightMatch(text, query) {
+ if (!query) return text;
+
+ const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
+ return text.replace(regex, '$1 ');
}
- }
-
- selectOption(option) {
- this.isUpdatingSelect = true;
- this.input.value = option.text;
- this.select.value = option.value;
- this.showSelected(option.text);
- this.hideResults();
-
- // Trigger change event on the hidden select
- this.select.dispatchEvent(new Event('change', { bubbles: true }));
- this.isUpdatingSelect = false;
- }
-
- showSelected(text) {
- const selectedText = this.selected.querySelector('.selected-text');
- if (selectedText) {
- selectedText.textContent = text;
+
+ highlightNext() {
+ this.highlightedIndex = Math.min(this.highlightedIndex + 1, this.filteredOptions.length - 1);
+ this.updateHighlight();
+ }
+
+ highlightPrevious() {
+ this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
+ this.updateHighlight();
}
- this.selected.style.display = 'flex';
- this.input.style.display = 'none';
- }
-
- clearSelection() {
- if (this.isUpdatingSelect) return; // Prevent infinite loops
-
- this.isUpdatingSelect = true;
- this.input.value = '';
- this.select.value = '';
- this.selected.style.display = 'none';
- this.input.style.display = 'block';
- this.hideResults();
-
- // Focus the input
- if (!this.input.disabled) {
- this.input.focus();
+
+ updateHighlight() {
+ this.results.querySelectorAll('.search-dropdown-item').forEach((item, index) => {
+ item.classList.toggle('highlighted', index === this.highlightedIndex);
+ });
}
-
- // Trigger change event on the hidden select
- this.select.dispatchEvent(new Event('change', { bubbles: true }));
- this.isUpdatingSelect = false;
- }
-
- syncFromSelect() {
- if (this.isUpdatingSelect) return; // Prevent infinite loops
-
- // If the select was changed externally, update the search input
- const selectedOption = this.select.selectedOptions[0];
- if (selectedOption && selectedOption.value) {
- this.input.value = selectedOption.text;
- this.showSelected(selectedOption.text);
- } else {
- this.isUpdatingSelect = true;
- this.input.value = '';
- this.selected.style.display = 'none';
- this.input.style.display = 'block';
- this.hideResults();
- this.isUpdatingSelect = false;
+
+ selectHighlighted() {
+ if (this.highlightedIndex >= 0 && this.highlightedIndex < this.filteredOptions.length) {
+ this.selectOption(this.filteredOptions[this.highlightedIndex]);
+ }
}
- }
-
- showResults() {
- this.results.style.display = 'block';
- }
-
- hideResults() {
- this.results.style.display = 'none';
- this.highlightedIndex = -1;
- }
-
- isResultsVisible() {
- return this.results.style.display !== 'none';
- }
-
- enable() {
- this.input.disabled = false;
- this.select.disabled = false;
- this.input.placeholder = this.options.placeholder;
- }
-
- disable() {
- this.input.disabled = true;
- this.select.disabled = true;
- this.input.placeholder = 'Select dependencies first';
- this.clearSelection();
- this.hideResults();
- }
-
- reset() {
- this.clearSelection();
- this.allOptions = [];
- this.filteredOptions = [];
- this.select.innerHTML = `Select ${this.getFieldLabel()} `;
- }
-
- getValue() {
- return this.select.value;
- }
-
- setValue(value, triggerChange = true) {
- const option = this.allOptions.find(opt => opt.value === value);
- if (option) {
- this.selectOption(option);
- if (!triggerChange) {
- // If we don't want to trigger change, we need to prevent it
- this.select.value = value;
+
+ selectOption(option) {
+ this.isUpdatingSelect = true;
this.input.value = option.text;
+ this.select.value = option.value;
this.showSelected(option.text);
- }
+ this.hideResults();
+
+ // Trigger change event on the hidden select
+ this.select.dispatchEvent(new Event('change', {
+ bubbles: true
+ }));
+ this.isUpdatingSelect = false;
+ }
+
+ showSelected(text) {
+ const selectedText = this.selected.querySelector('.selected-text');
+ if (selectedText) {
+ selectedText.textContent = text;
+ }
+ this.selected.style.display = 'flex';
+ this.input.style.display = 'none';
+ }
+
+ clearSelection() {
+ if (this.isUpdatingSelect) return; // Prevent infinite loops
+
+ this.isUpdatingSelect = true;
+ this.input.value = '';
+ this.select.value = '';
+ this.selected.style.display = 'none';
+ this.input.style.display = 'block';
+ this.hideResults();
+
+ // Focus the input
+ if (!this.input.disabled) {
+ this.input.focus();
+ }
+
+ // Trigger change event on the hidden select
+ this.select.dispatchEvent(new Event('change', {
+ bubbles: true
+ }));
+ this.isUpdatingSelect = false;
+ }
+
+ syncFromSelect() {
+ if (this.isUpdatingSelect) return; // Prevent infinite loops
+
+ // If the select was changed externally, update the search input
+ const selectedOption = this.select.selectedOptions[0];
+ if (selectedOption && selectedOption.value) {
+ this.input.value = selectedOption.text;
+ this.showSelected(selectedOption.text);
+ } else {
+ this.isUpdatingSelect = true;
+ this.input.value = '';
+ this.selected.style.display = 'none';
+ this.input.style.display = 'block';
+ this.hideResults();
+ this.isUpdatingSelect = false;
+ }
+ }
+
+ showResults() {
+ this.results.style.display = 'block';
+ }
+
+ hideResults() {
+ this.results.style.display = 'none';
+ this.highlightedIndex = -1;
+ }
+
+ isResultsVisible() {
+ return this.results.style.display !== 'none';
+ }
+
+ enable() {
+ this.input.disabled = false;
+ this.select.disabled = false;
+ this.input.placeholder = this.options.placeholder;
+ }
+
+ disable() {
+ this.input.disabled = true;
+ this.select.disabled = true;
+ this.input.placeholder = 'Select dependencies first';
+ this.clearSelection();
+ this.hideResults();
+ }
+
+ reset() {
+ this.clearSelection();
+ this.allOptions = [];
+ this.filteredOptions = [];
+ this.select.innerHTML = `Select ${this.getFieldLabel()} `;
+ }
+
+ getValue() {
+ return this.select.value;
+ }
+
+ setValue(value, triggerChange = true) {
+ const option = this.allOptions.find(opt => opt.value === value);
+ if (option) {
+ this.selectOption(option);
+ if (!triggerChange) {
+ // If we don't want to trigger change, we need to prevent it
+ this.select.value = value;
+ this.input.value = option.text;
+ this.showSelected(option.text);
+ }
+ }
+ }
+
+ getFieldLabel() {
+ const label = this.container?.previousElementSibling?.querySelector('label')?.textContent;
+ return label ? label.replace('*', '').trim() : 'Option';
+ }
+
+ // Static method to create multiple search dropdowns
+ static createMultiple(fieldIds, options = {}) {
+ const instances = {};
+ fieldIds.forEach(fieldId => {
+ instances[fieldId] = new SearchDropdown(fieldId, options[fieldId] || {});
+ });
+ return instances;
}
- }
-
- getFieldLabel() {
- const label = this.container?.previousElementSibling?.querySelector('label')?.textContent;
- return label ? label.replace('*', '').trim() : 'Option';
- }
-
- // Static method to create multiple search dropdowns
- static createMultiple(fieldIds, options = {}) {
- const instances = {};
- fieldIds.forEach(fieldId => {
- instances[fieldId] = new SearchDropdown(fieldId, options[fieldId] || {});
- });
- return instances;
- }
}
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
- module.exports = SearchDropdown;
+ module.exports = SearchDropdown;
}
// Make available globally
-window.SearchDropdown = SearchDropdown;
+window.SearchDropdown = SearchDropdown;
\ No newline at end of file
diff --git a/efile_app/efile/static/js/dynamic-form-sections.js b/efile_app/efile/static/js/dynamic-form-sections.js
index 34a5a23..b913fce 100644
--- a/efile_app/efile/static/js/dynamic-form-sections.js
+++ b/efile_app/efile/static/js/dynamic-form-sections.js
@@ -1,781 +1,781 @@
class DynamicFormSections {
- constructor() {
- // We create parties/services containers dynamically inside #dynamicSections
- this.caseInfoContainer = null; // will be created when needed
- this.partiesContainer = null; // will be created when needed
- this.partiesHeader = null; // will be created when needed
- this.servicesContainer = null; // will be created when needed
- this.dynamicSections = document.getElementById("dynamicSections");
- this.currentCaseType = null;
- this.config = null;
- this.preservedFormData = null;
- this.preservedCaseType = null;
-
- this.jurisdiction = apiUtils.getCurrentJurisdiction();
- this.init();
- }
-
- async init() {
- // Load configuration from server
- await this.loadConfiguration();
-
- // Load any existing case data from the session
- await this.loadExistingCaseData();
-
- // Listen for case type changes to trigger form section updates
- const caseTypeSelect = document.getElementById("case_type");
- if (caseTypeSelect) {
- caseTypeSelect.addEventListener("change", () => {
- this.handleCaseTypeChange();
- });
-
- // Also listen for when the dropdown is cleared/reset
- const observer = new MutationObserver((mutations) => {
- mutations.forEach((mutation) => {
- if (mutation.type === "childList" || mutation.type === "attributes") {
- // Check if dropdown was cleared
- if (
- caseTypeSelect.value === "" &&
- this.dynamicSections &&
- this.dynamicSections.style.display === "block"
- ) {
- this.hideDynamicSections();
- }
- }
- });
- });
- observer.observe(caseTypeSelect, {
- childList: true,
- attributes: true,
- attributeFilter: ["value"],
- });
- }
+ constructor() {
+ // We create parties/services containers dynamically inside #dynamicSections
+ this.caseInfoContainer = null; // will be created when needed
+ this.partiesContainer = null; // will be created when needed
+ this.partiesHeader = null; // will be created when needed
+ this.servicesContainer = null; // will be created when needed
+ this.dynamicSections = document.getElementById("dynamicSections");
+ this.currentCaseType = null;
+ this.config = null;
+ this.preservedFormData = null;
+ this.preservedCaseType = null;
- // Listen for existing case changes to show/hide case information
- const existingCaseSelect = document.getElementById("existing_case");
- if (existingCaseSelect) {
- existingCaseSelect.addEventListener("change", () => {
- this.handleExistingCaseChange();
- });
+ this.jurisdiction = apiUtils.getCurrentJurisdiction();
+ this.init();
}
- // Listen for court changes to reload configuration with court-specific modifications
- const courtSelect = document.getElementById("court");
- if (courtSelect) {
- courtSelect.addEventListener("change", () => {
- // If we have a case type selected, reload the configuration and re-render
- if (caseTypeSelect && caseTypeSelect.value) {
- this.handleCaseTypeChange();
+ async init() {
+ // Load configuration from server
+ await this.loadConfiguration();
+
+ // Load any existing case data from the session
+ await this.loadExistingCaseData();
+
+ // Listen for case type changes to trigger form section updates
+ const caseTypeSelect = document.getElementById("case_type");
+ if (caseTypeSelect) {
+ caseTypeSelect.addEventListener("change", () => {
+ this.handleCaseTypeChange();
+ });
+
+ // Also listen for when the dropdown is cleared/reset
+ const observer = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ if (mutation.type === "childList" || mutation.type === "attributes") {
+ // Check if dropdown was cleared
+ if (
+ caseTypeSelect.value === "" &&
+ this.dynamicSections &&
+ this.dynamicSections.style.display === "block"
+ ) {
+ this.hideDynamicSections();
+ }
+ }
+ });
+ });
+ observer.observe(caseTypeSelect, {
+ childList: true,
+ attributes: true,
+ attributeFilter: ["value"],
+ });
}
- });
- }
- }
-
- async loadConfiguration() {
- try {
- // Get current court selection to include in the request
- const courtDropdown = document.getElementById("court");
- const caseTypeDropdown = document.getElementById("case_type");
- const court = courtDropdown ? courtDropdown.value : "";
- const caseTypeValue = caseTypeDropdown ? caseTypeDropdown.value : "";
- const caseTypeText =
- caseTypeDropdown && caseTypeDropdown.selectedIndex >= 0
- ? caseTypeDropdown.options[caseTypeDropdown.selectedIndex].text
- : "";
-
- // Only make API call if we have a case type (required parameter)
- if (!caseTypeValue || !caseTypeText) {
- this.config = this.getDefaultConfig();
- return;
- }
-
- // Use form-config endpoint which applies court-specific modifications
- let url = "/api/form-config/";
- let params = {
- "case_type": caseTypeText,
- "jurisdiction": this.jurisdiction,
- "court": court
- };
-
- const result = await apiUtils.get(url, params);
-
- if (result.success && result.data) {
- // Transform the response to match the expected config structure
- this.config = {
- case_types: {},
- base_case_types: {},
- };
- // If we have sections, create a case type config structure
- if (
- result.data.sections &&
- Object.keys(result.data.sections).length > 0
- ) {
- // Determine if this is a base case type by checking common patterns
- const isBaseType =
- result.data.case_type_name &&
- (result.data.case_type_name.toLowerCase().includes("eviction") ||
- result.data.case_type_name
- .toLowerCase()
- .includes("repossession") ||
- result.data.case_type_name.toLowerCase().includes("restoration"));
-
- const caseConfig = {
- keywords: this.extractKeywordsFromCaseType(
- result.data.case_type_name
- ),
- sections: result.data.sections,
- description: result.data.description || "",
- validation_rules: result.data.validation_rules || [],
- };
-
- // Put eviction/repossession types in base_case_types, others in case_types
- if (isBaseType) {
- this.config.base_case_types.eviction_repossession = caseConfig;
- } else {
- this.config.case_types[
- result.data.case_type_name || "name_change"
- ] = caseConfig;
- }
+ // Listen for existing case changes to show/hide case information
+ const existingCaseSelect = document.getElementById("existing_case");
+ if (existingCaseSelect) {
+ existingCaseSelect.addEventListener("change", () => {
+ this.handleExistingCaseChange();
+ });
+ }
+
+ // Listen for court changes to reload configuration with court-specific modifications
+ const courtSelect = document.getElementById("court");
+ if (courtSelect) {
+ courtSelect.addEventListener("change", () => {
+ // If we have a case type selected, reload the configuration and re-render
+ if (caseTypeSelect && caseTypeSelect.value) {
+ this.handleCaseTypeChange();
+ }
+ });
}
- } else {
- console.error(
- "Failed to load configuration:",
- result.error || "Unknown error"
- );
- this.config = this.getDefaultConfig();
- }
-
- let party_params = {"case_type": caseTypeValue, "jurisdiction": this.jurisdiction, "court": court, "existing_case": "no"};
- const parties_result = await apiUtils.getPartyTypes(party_params);
- if (parties_result.success && parties_result.party_types) {
- this.parties = parties_result.party_types;
- }
- } catch (error) {
- console.error("Error loading configuration:", error);
- this.config = this.getDefaultConfig();
}
- }
-
- // Helper method to extract keywords from case type name
- extractKeywordsFromCaseType(caseTypeName) {
- if (!caseTypeName) return [];
- const name = caseTypeName.toLowerCase();
- if (name.includes("eviction") || name.includes("repossession")) {
- return ["eviction", "repossession", "restoration"];
+
+ async loadConfiguration() {
+ try {
+ // Get current court selection to include in the request
+ const courtDropdown = document.getElementById("court");
+ const caseTypeDropdown = document.getElementById("case_type");
+ const court = courtDropdown ? courtDropdown.value : "";
+ const caseTypeValue = caseTypeDropdown ? caseTypeDropdown.value : "";
+ const caseTypeText =
+ caseTypeDropdown && caseTypeDropdown.selectedIndex >= 0 ?
+ caseTypeDropdown.options[caseTypeDropdown.selectedIndex].text :
+ "";
+
+ // Only make API call if we have a case type (required parameter)
+ if (!caseTypeValue || !caseTypeText) {
+ this.config = this.getDefaultConfig();
+ return;
+ }
+
+ // Use form-config endpoint which applies court-specific modifications
+ let url = "/api/form-config/";
+ let params = {
+ "case_type": caseTypeText,
+ "jurisdiction": this.jurisdiction,
+ "court": court
+ };
+
+ const result = await apiUtils.get(url, params);
+
+ if (result.success && result.data) {
+ // Transform the response to match the expected config structure
+ this.config = {
+ case_types: {},
+ base_case_types: {},
+ };
+
+ // If we have sections, create a case type config structure
+ if (
+ result.data.sections &&
+ Object.keys(result.data.sections).length > 0
+ ) {
+ // Determine if this is a base case type by checking common patterns
+ const isBaseType =
+ result.data.case_type_name &&
+ (result.data.case_type_name.toLowerCase().includes("eviction") ||
+ result.data.case_type_name
+ .toLowerCase()
+ .includes("repossession") ||
+ result.data.case_type_name.toLowerCase().includes("restoration"));
+
+ const caseConfig = {
+ keywords: this.extractKeywordsFromCaseType(
+ result.data.case_type_name
+ ),
+ sections: result.data.sections,
+ description: result.data.description || "",
+ validation_rules: result.data.validation_rules || [],
+ };
+
+ // Put eviction/repossession types in base_case_types, others in case_types
+ if (isBaseType) {
+ this.config.base_case_types.eviction_repossession = caseConfig;
+ } else {
+ this.config.case_types[
+ result.data.case_type_name || "name_change"
+ ] = caseConfig;
+ }
+ }
+ } else {
+ console.error(
+ "Failed to load configuration:",
+ result.error || "Unknown error"
+ );
+ this.config = this.getDefaultConfig();
+ }
+
+ let party_params = {
+ "case_type": caseTypeValue,
+ "jurisdiction": this.jurisdiction,
+ "court": court,
+ "existing_case": "no"
+ };
+ const parties_result = await apiUtils.getPartyTypes(party_params);
+ if (parties_result.success && parties_result.party_types) {
+ this.parties = parties_result.party_types;
+ }
+ } catch (error) {
+ console.error("Error loading configuration:", error);
+ this.config = this.getDefaultConfig();
+ }
}
- if (name.includes("name") && name.includes("change")) {
- return ["name change", "name petition", "change of name"];
+
+ // Helper method to extract keywords from case type name
+ extractKeywordsFromCaseType(caseTypeName) {
+ if (!caseTypeName) return [];
+ const name = caseTypeName.toLowerCase();
+ if (name.includes("eviction") || name.includes("repossession")) {
+ return ["eviction", "repossession", "restoration"];
+ }
+ if (name.includes("name") && name.includes("change")) {
+ return ["name change", "name petition", "change of name"];
+ }
+ return [caseTypeName.toLowerCase()];
}
- return [caseTypeName.toLowerCase()];
- }
-
- async loadExistingCaseData() {
- try {
- // Check if there's an API endpoint to retrieve saved case data
- const result = await apiUtils.getCaseData();
- if (
- result.success &&
- result.data &&
- Object.keys(result.data).length > 0
- ) {
- this.restorationData = result.data;
-
- // Also make it available for form validation system
- if (window.formValidation) {
- window.formValidation.restorationData = result.data;
+
+ async loadExistingCaseData() {
+ try {
+ // Check if there's an API endpoint to retrieve saved case data
+ const result = await apiUtils.getCaseData();
+ if (
+ result.success &&
+ result.data &&
+ Object.keys(result.data).length > 0
+ ) {
+ this.restorationData = result.data;
+
+ // Also make it available for form validation system
+ if (window.formValidation) {
+ window.formValidation.restorationData = result.data;
+ }
+ }
+ } catch (error) {
+ console.warn("Could not load existing case data:", error);
+ // This is not a critical error, continue without saved data
}
- }
- } catch (error) {
- console.warn("Could not load existing case data:", error);
- // This is not a critical error, continue without saved data
}
- }
-
- getDefaultConfig() {
- // Fallback configuration if server config fails
- return {
- case_types: {
- name_change: {
- keywords: ["name change"],
- sections: {
- parties: {
- title: "Required parties",
- fields: [
- {
- section_title: "Petitioner",
- required: true,
- fields: [
- {
- name: "petitioner_first_name",
- label: "First Name",
- type: "text",
- required: true,
- column_width: "col-md-6",
- },
- {
- name: "petitioner_last_name",
- label: "Last Name",
- type: "text",
- required: true,
- column_width: "col-md-6",
+
+ getDefaultConfig() {
+ // Fallback configuration if server config fails
+ return {
+ case_types: {
+ name_change: {
+ keywords: ["name change"],
+ sections: {
+ parties: {
+ title: "Required parties",
+ fields: [{
+ section_title: "Petitioner",
+ required: true,
+ fields: [{
+ name: "petitioner_first_name",
+ label: "First Name",
+ type: "text",
+ required: true,
+ column_width: "col-md-6",
+ }, {
+ name: "petitioner_last_name",
+ label: "Last Name",
+ type: "text",
+ required: true,
+ column_width: "col-md-6",
+ }, ],
+ }, ],
+ },
},
- ],
},
- ],
},
- },
- },
- },
- base_case_types: {},
- };
- }
-
- async handleCaseTypeChange() {
- const caseTypeSelect = document.getElementById("case_type");
-
- if (!caseTypeSelect) {
- return;
+ base_case_types: {},
+ };
}
- const caseTypeText =
- caseTypeSelect.options[caseTypeSelect.selectedIndex]?.text || "";
- const caseTypeValue = caseTypeSelect.value;
+ async handleCaseTypeChange() {
+ const caseTypeSelect = document.getElementById("case_type");
- // Don't hide sections immediately if the dropdown is being cleared - give time for restoration
- if (!caseTypeValue) {
- // Check if this is a temporary clearing (cascading dropdown coordination)
- const isBeingCleared =
- caseTypeSelect.disabled ||
- window.cascadingDropdowns?.optionalServicesLoaded === false;
+ if (!caseTypeSelect) {
+ return;
+ }
- if (isBeingCleared) {
- // Set a timeout to check again later
- setTimeout(() => {
- if (!caseTypeSelect.value && caseTypeSelect.options.length <= 1) {
+ const caseTypeText =
+ caseTypeSelect.options[caseTypeSelect.selectedIndex]?.text || "";
+ const caseTypeValue = caseTypeSelect.value;
+
+ // Don't hide sections immediately if the dropdown is being cleared - give time for restoration
+ if (!caseTypeValue) {
+ // Check if this is a temporary clearing (cascading dropdown coordination)
+ const isBeingCleared =
+ caseTypeSelect.disabled ||
+ window.cascadingDropdowns?.optionalServicesLoaded === false;
+
+ if (isBeingCleared) {
+ // Set a timeout to check again later
+ setTimeout(() => {
+ if (!caseTypeSelect.value && caseTypeSelect.options.length <= 1) {
+ this.hideDynamicSections();
+ } else if (caseTypeSelect.value) {
+ this.handleCaseTypeChange();
+ }
+ }, 1000); // Wait 1 second for cascading system to restore values
+ return;
+ } else {
+ this.hideDynamicSections();
+ return;
+ }
+ }
+
+ // Reload configuration with current court and case type to get court-specific modifications
+ await this.loadConfiguration();
+
+ if (!this.config) {
+ return;
+ }
+
+ const caseTypeConfig = this.findCaseTypeConfig(caseTypeText);
+
+ if (caseTypeConfig) {
+ this.currentCaseType = caseTypeValue; // Track the current case type
+ this.renderCaseTypeForm(caseTypeConfig);
+ this.showDynamicSections();
+ } else {
this.hideDynamicSections();
- } else if (caseTypeSelect.value) {
- this.handleCaseTypeChange();
- }
- }, 1000); // Wait 1 second for cascading system to restore values
- return;
- } else {
- this.hideDynamicSections();
- return;
- }
+ }
}
- // Reload configuration with current court and case type to get court-specific modifications
- await this.loadConfiguration();
+ handleExistingCaseChange() {
+ const existingCaseSelect = document.getElementById("existing_case");
+ if (!existingCaseSelect) {
+ return;
+ }
- if (!this.config) {
- return;
+ const existingCaseValue = existingCaseSelect.value;
+
+ // Show/hide case information section based on existing case selection
+ if (this.caseInfoContainer) {
+ if (existingCaseValue === "yes") {
+ // Show case information section
+ this.caseInfoContainer.style.display = "block";
+ // Also show the header if it exists
+ const caseInfoHeader = this.caseInfoContainer.previousElementSibling;
+ if (caseInfoHeader && caseInfoHeader.tagName === "H3") {
+ caseInfoHeader.style.display = "block";
+ }
+ } else {
+ // Hide case information section
+ this.caseInfoContainer.style.display = "none";
+ // Also hide the header if it exists
+ const caseInfoHeader = this.caseInfoContainer.previousElementSibling;
+ if (caseInfoHeader && caseInfoHeader.tagName === "H3") {
+ caseInfoHeader.style.display = "none";
+ }
+ }
+ }
}
- const caseTypeConfig = this.findCaseTypeConfig(caseTypeText);
+ findCaseTypeConfig(caseTypeText) {
+ const lowerCaseText = caseTypeText.toLowerCase();
- if (caseTypeConfig) {
- this.currentCaseType = caseTypeValue; // Track the current case type
- this.renderCaseTypeForm(caseTypeConfig);
- this.showDynamicSections();
- } else {
- this.hideDynamicSections();
- }
- }
+ // Also try to match by dropdown value if text matching fails
+ const caseTypeSelect = document.getElementById("case_type");
+ const caseTypeValue = caseTypeSelect ? caseTypeSelect.value : "";
+
+ // Search both case_types and base_case_types sections
+ const caseTypeSources = [
+ this.config.case_types || {},
+ this.config.base_case_types || {},
+ ];
+
+ for (const caseTypes of caseTypeSources) {
+ for (const [configKey, caseConfig] of Object.entries(caseTypes)) {
+ const keywords = caseConfig.keywords || [];
+
+ // Method 1: Check if any keyword matches the case type text
+ const matchingKeyword = keywords.find((keyword) => {
+ const lowerKeyword = keyword.toLowerCase();
+ const matches = lowerCaseText.includes(lowerKeyword);
+ return matches;
+ });
- handleExistingCaseChange() {
- const existingCaseSelect = document.getElementById("existing_case");
- if (!existingCaseSelect) {
- return;
- }
+ if (matchingKeyword) {
+ return caseConfig;
+ }
+
+ // Method 2: Direct config key matching (fallback)
+ if (
+ configKey === "name_change" &&
+ (lowerCaseText.includes("name") ||
+ lowerCaseText.includes("change") ||
+ caseTypeValue.toLowerCase().includes("name"))
+ ) {
+ return caseConfig;
+ }
- const existingCaseValue = existingCaseSelect.value;
-
- // Show/hide case information section based on existing case selection
- if (this.caseInfoContainer) {
- if (existingCaseValue === "yes") {
- // Show case information section
- this.caseInfoContainer.style.display = "block";
- // Also show the header if it exists
- const caseInfoHeader = this.caseInfoContainer.previousElementSibling;
- if (caseInfoHeader && caseInfoHeader.tagName === "H3") {
- caseInfoHeader.style.display = "block";
+ // Method 3: Check for eviction case type
+ if (
+ configKey === "eviction_repossession" &&
+ (lowerCaseText.includes("eviction") ||
+ lowerCaseText.includes("repossession") ||
+ lowerCaseText.includes("restoration"))
+ ) {
+ return caseConfig;
+ }
+ }
}
- } else {
- // Hide case information section
- this.caseInfoContainer.style.display = "none";
- // Also hide the header if it exists
- const caseInfoHeader = this.caseInfoContainer.previousElementSibling;
- if (caseInfoHeader && caseInfoHeader.tagName === "H3") {
- caseInfoHeader.style.display = "none";
+
+ // Method 4: If no match found, try some common patterns
+ if (lowerCaseText.includes("name") && lowerCaseText.includes("change")) {
+ if (this.config.case_types && this.config.case_types.name_change) {
+ return this.config.case_types.name_change;
+ }
+ if (
+ this.config.base_case_types &&
+ this.config.base_case_types.name_change
+ ) {
+ return this.config.base_case_types.name_change;
+ }
}
- }
+
+ return null;
}
- }
-
- findCaseTypeConfig(caseTypeText) {
- const lowerCaseText = caseTypeText.toLowerCase();
-
- // Also try to match by dropdown value if text matching fails
- const caseTypeSelect = document.getElementById("case_type");
- const caseTypeValue = caseTypeSelect ? caseTypeSelect.value : "";
-
- // Search both case_types and base_case_types sections
- const caseTypeSources = [
- this.config.case_types || {},
- this.config.base_case_types || {},
- ];
-
- for (const caseTypes of caseTypeSources) {
- for (const [configKey, caseConfig] of Object.entries(caseTypes)) {
- const keywords = caseConfig.keywords || [];
-
- // Method 1: Check if any keyword matches the case type text
- const matchingKeyword = keywords.find((keyword) => {
- const lowerKeyword = keyword.toLowerCase();
- const matches = lowerCaseText.includes(lowerKeyword);
- return matches;
- });
- if (matchingKeyword) {
- return caseConfig;
+ renderCaseTypeForm(caseTypeConfig) {
+ this.preserveCurrentState();
+
+ const sections = caseTypeConfig.sections || {};
+
+ // Render case information section (should come first)
+ if (sections.case_information) {
+ this.renderSection("case_information", sections.case_information || {});
}
- // Method 2: Direct config key matching (fallback)
- if (
- configKey === "name_change" &&
- (lowerCaseText.includes("name") ||
- lowerCaseText.includes("change") ||
- caseTypeValue.toLowerCase().includes("name"))
- ) {
- return caseConfig;
+ // Render parties section
+ if (sections.parties) {
+ this.renderSection("parties", sections.parties || {});
}
- // Method 3: Check for eviction case type
- if (
- configKey === "eviction_repossession" &&
- (lowerCaseText.includes("eviction") ||
- lowerCaseText.includes("repossession") ||
- lowerCaseText.includes("restoration"))
- ) {
- return caseConfig;
+ // Render services section
+ if (sections.services) {
+ this.renderSection("services", sections.services || {});
}
- }
- }
- // Method 4: If no match found, try some common patterns
- if (lowerCaseText.includes("name") && lowerCaseText.includes("change")) {
- if (this.config.case_types && this.config.case_types.name_change) {
- return this.config.case_types.name_change;
- }
- if (
- this.config.base_case_types &&
- this.config.base_case_types.name_change
- ) {
- return this.config.base_case_types.name_change;
- }
- }
+ // Restore preserved state after rendering
+ this.restorePreservedState();
- return null;
- }
+ // Load party type dropdowns after all sections are rendered
+ this.loadPartyTypeDropdowns();
- renderCaseTypeForm(caseTypeConfig) {
- this.preserveCurrentState();
+ // Update form validation after rendering
+ this.updateFormValidation();
- const sections = caseTypeConfig.sections || {};
+ // Check if there's restoration data from form validation that needs to be applied
+ if (this.restorationData) {
+ //setTimeout(() => {
+ this.populateRenderedFields(this.restorationData);
+ this.restorationData = null; // Clear after use
+ //}, 50);
+ }
- // Render case information section (should come first)
- if (sections.case_information) {
- this.renderSection("case_information", sections.case_information || {});
- }
+ // Notify form validation system that dynamic fields have been rendered
+ setTimeout(() => {
+ if (window.formValidation && window.formValidation.restorationData) {
+ window.formValidation.populateDynamicFields(
+ window.formValidation.restorationData
+ );
+ }
- // Render parties section
- if (sections.parties) {
- this.renderSection("parties", sections.parties || {});
+ // Check existing case selection to show/hide case information appropriately
+ this.handleExistingCaseChange();
+ }, 100);
}
- // Render services section
- if (sections.services) {
- this.renderSection("services", sections.services || {});
- }
+ renderSection(sectionType, sectionConfig) {
+ // Ensure containers exist inside the dynamicSections wrapper
+ if (!this.dynamicSections) {
+ return;
+ }
- // Restore preserved state after rendering
- this.restorePreservedState();
+ if (sectionType === "case_information") {
+ if (!this.caseInfoContainer) {
+ // Create header and container for case information
+ const header = document.createElement("h3");
+ header.className = "subsection-header";
+ header.textContent = sectionConfig.title || getText("Case Information");
- // Load party type dropdowns after all sections are rendered
- this.loadPartyTypeDropdowns();
+ const containerDiv = document.createElement("div");
+ containerDiv.id = "caseInfoContainer";
- // Update form validation after rendering
- this.updateFormValidation();
+ this.dynamicSections.appendChild(header);
+ this.dynamicSections.appendChild(containerDiv);
+ this.caseInfoContainer = containerDiv;
- // Check if there's restoration data from form validation that needs to be applied
- if (this.restorationData) {
- //setTimeout(() => {
- this.populateRenderedFields(this.restorationData);
- this.restorationData = null; // Clear after use
- //}, 50);
- }
+ // Initially hide case information section - only show when existing_case = "yes"
+ header.style.display = "none";
+ containerDiv.style.display = "none";
+ }
+ } else if (sectionType === "parties") {
+ if (!this.partiesContainer) {
+ // Create header and container for parties
+ const header = document.createElement("h3");
+ header.className = "subsection-header";
+ header.textContent = sectionConfig.title || getText("Required Parties");
+
+ const containerDiv = document.createElement("div");
+ containerDiv.id = "partiesContainer";
+
+ this.dynamicSections.appendChild(header);
+ this.dynamicSections.appendChild(containerDiv);
+ this.partiesContainer = containerDiv;
+ this.partiesHeader = header; // Store reference to header for visibility control
+ }
+ } else if (sectionType === "services") {
+ if (!this.servicesContainer) {
+ const containerDiv = document.createElement("div");
+ containerDiv.id = "servicesContainer";
+ this.dynamicSections.appendChild(containerDiv);
+ this.servicesContainer = containerDiv;
+ }
+ }
- // Notify form validation system that dynamic fields have been rendered
- setTimeout(() => {
- if (window.formValidation && window.formValidation.restorationData) {
- window.formValidation.populateDynamicFields(
- window.formValidation.restorationData
- );
- }
-
- // Check existing case selection to show/hide case information appropriately
- this.handleExistingCaseChange();
- }, 100);
- }
-
- renderSection(sectionType, sectionConfig) {
- // Ensure containers exist inside the dynamicSections wrapper
- if (!this.dynamicSections) {
- return;
- }
+ const container =
+ sectionType === "case_information" ?
+ this.caseInfoContainer :
+ sectionType === "parties" ?
+ this.partiesContainer :
+ this.servicesContainer;
+
+ let html = "";
+
+ if (sectionType === "case_information") {
+ html = this.renderCaseInformationSection(sectionConfig);
+ } else if (sectionType === "parties") {
+ html = this.renderPartiesSection(sectionConfig);
+ } else if (sectionType === "services") {
+ html = this.renderServicesSection(sectionConfig);
+ }
- if (sectionType === "case_information") {
- if (!this.caseInfoContainer) {
- // Create header and container for case information
- const header = document.createElement("h3");
- header.className = "subsection-header";
- header.textContent = sectionConfig.title || getText("Case Information");
-
- const containerDiv = document.createElement("div");
- containerDiv.id = "caseInfoContainer";
-
- this.dynamicSections.appendChild(header);
- this.dynamicSections.appendChild(containerDiv);
- this.caseInfoContainer = containerDiv;
-
- // Initially hide case information section - only show when existing_case = "yes"
- header.style.display = "none";
- containerDiv.style.display = "none";
- }
- } else if (sectionType === "parties") {
- if (!this.partiesContainer) {
- // Create header and container for parties
- const header = document.createElement("h3");
- header.className = "subsection-header";
- header.textContent = sectionConfig.title || getText("Required Parties");
-
- const containerDiv = document.createElement("div");
- containerDiv.id = "partiesContainer";
-
- this.dynamicSections.appendChild(header);
- this.dynamicSections.appendChild(containerDiv);
- this.partiesContainer = containerDiv;
- this.partiesHeader = header; // Store reference to header for visibility control
- }
- } else if (sectionType === "services") {
- if (!this.servicesContainer) {
- const containerDiv = document.createElement("div");
- containerDiv.id = "servicesContainer";
- this.dynamicSections.appendChild(containerDiv);
- this.servicesContainer = containerDiv;
- }
- }
+ container.innerHTML = html;
- const container =
- sectionType === "case_information"
- ? this.caseInfoContainer
- : sectionType === "parties"
- ? this.partiesContainer
- : this.servicesContainer;
-
- let html = "";
-
- if (sectionType === "case_information") {
- html = this.renderCaseInformationSection(sectionConfig);
- } else if (sectionType === "parties") {
- html = this.renderPartiesSection(sectionConfig);
- } else if (sectionType === "services") {
- html = this.renderServicesSection(sectionConfig);
- }
+ // Special handling for parties section - hide header if no content
+ if (sectionType === "parties" && this.partiesHeader) {
+ if (html.trim() === "") {
+ // No parties sections were rendered, hide the header
+ this.partiesHeader.style.display = "none";
+ } else {
+ // There is content, show the header
+ this.partiesHeader.style.display = "block";
+ }
+ }
- container.innerHTML = html;
-
- // Special handling for parties section - hide header if no content
- if (sectionType === "parties" && this.partiesHeader) {
- if (html.trim() === "") {
- // No parties sections were rendered, hide the header
- this.partiesHeader.style.display = "none";
- } else {
- // There is content, show the header
- this.partiesHeader.style.display = "block";
- }
+ // Note: loadPartyTypeDropdowns() and updateFormValidation() are called
+ // once after all sections are rendered in renderCaseTypeForm()
}
- // Note: loadPartyTypeDropdowns() and updateFormValidation() are called
- // once after all sections are rendered in renderCaseTypeForm()
- }
-
- renderCaseInformationSection(sectionConfig) {
- // Case information has the same nested structure as parties: section_title -> fields
- const sectionGroups = sectionConfig.fields || [];
-
- let html = "";
-
- // Iterate through each section group (like "Case Details")
- sectionGroups.forEach((sectionGroup) => {
- if (sectionGroup.section_title) {
- // Check if this section should be required
- const isRequired = sectionGroup.required || false;
- const requiredIndicator = isRequired
- ? '* '
- : "";
- const optionalIndicator = !isRequired
- ? '(Optional) '
- : "";
-
- html += `
+ renderCaseInformationSection(sectionConfig) {
+ // Case information has the same nested structure as parties: section_title -> fields
+ const sectionGroups = sectionConfig.fields || [];
+
+ let html = "";
+
+ // Iterate through each section group (like "Case Details")
+ sectionGroups.forEach((sectionGroup) => {
+ if (sectionGroup.section_title) {
+ // Check if this section should be required
+ const isRequired = sectionGroup.required || false;
+ const requiredIndicator = isRequired ?
+ '
* ' :
+ "";
+ const optionalIndicator = !isRequired ?
+ '
(Optional) ' :
+ "";
+
+ html += `
${sectionGroup.section_title} ${requiredIndicator} ${optionalIndicator}
`;
- if (sectionGroup.fields && sectionGroup.fields.length > 0) {
- html += '
';
-
- // Render each field in this section
- sectionGroup.fields.forEach((field) => {
- if (field && field.name && field.type) {
- // Update field requirement based on section requirement
- const updatedField = {
- ...field,
- required: field.required && isRequired,
- };
- html += this.renderField(updatedField);
- } else {
- console.warn("⚠️ Invalid field structure:", field);
+ if (sectionGroup.fields && sectionGroup.fields.length > 0) {
+ html += '
';
+
+ // Render each field in this section
+ sectionGroup.fields.forEach((field) => {
+ if (field && field.name && field.type) {
+ // Update field requirement based on section requirement
+ const updatedField = {
+ ...field,
+ required: field.required && isRequired,
+ };
+ html += this.renderField(updatedField);
+ } else {
+ console.warn("⚠️ Invalid field structure:", field);
+ }
+ });
+
+ html += "
";
+ }
+
+ html += "
";
}
- });
+ });
+
+ return html;
+ }
+
+ renderPartiesSection(sectionConfig) {
+ const fields = sectionConfig.fields || [];
+ let html = "";
- html += "
";
+ let party_types = this.parties;
+
+ if (party_types.length <= 1) {
+ // Skip rendering
+ return "";
}
- html += "
";
- }
- });
+ fields.forEach((partyGroup) => {
+ if (partyGroup.section_title) {
+ // Check if this section should be shown based on current court selection
+ const shouldShow = this.shouldShowSection(partyGroup);
- return html;
- }
+ // Skip rendering this section entirely if it shouldn't be shown
+ if (!shouldShow) {
+ return "";
+ }
- renderPartiesSection(sectionConfig) {
- const fields = sectionConfig.fields || [];
- let html = "";
+ // Check if this section should be required based on current court selection
+ const isRequired = this.evaluateConditionalRequirement(partyGroup);
+ const requiredIndicator = isRequired ?
+ '* ' :
+ "";
- let party_types = this.parties;
+ // Add optional indicator if not required
+ const optionalIndicator = !isRequired ?
+ '(Optional) ' :
+ "";
- if (party_types.length <= 1) {
- // Skip rendering
- return "";
- }
+ html += `
+
${partyGroup.section_title} ${requiredIndicator} ${optionalIndicator}
`;
- fields.forEach((partyGroup) => {
- if (partyGroup.section_title) {
- // Check if this section should be shown based on current court selection
- const shouldShow = this.shouldShowSection(partyGroup);
+ if (partyGroup.fields && partyGroup.fields.length > 0) {
+ html += '
';
- // Skip rendering this section entirely if it shouldn't be shown
- if (!shouldShow) {
- return "";
- }
+ partyGroup.fields.forEach((field) => {
+ // Update field requirement based on section requirement
+ const updatedField = {
+ ...field,
+ required: field.required && isRequired,
+ };
+ html += this.renderField(updatedField);
+ });
- // Check if this section should be required based on current court selection
- const isRequired = this.evaluateConditionalRequirement(partyGroup);
- const requiredIndicator = isRequired
- ? '* '
- : "";
+ html += "
";
+ }
- // Add optional indicator if not required
- const optionalIndicator = !isRequired
- ? '
(Optional) '
- : "";
+ html += "
";
+ }
+ });
- html += `
-
${partyGroup.section_title} ${requiredIndicator} ${optionalIndicator}
`;
+ return html;
+ }
- if (partyGroup.fields && partyGroup.fields.length > 0) {
- html += '
';
+ shouldShowSection(partyGroup) {
+ // If no conditional requirements defined, show by default
+ if (!partyGroup.conditional_requirements) {
+ return true;
+ }
- partyGroup.fields.forEach((field) => {
- // Update field requirement based on section requirement
- const updatedField = {
- ...field,
- required: field.required && isRequired,
- };
- html += this.renderField(updatedField);
- });
+ // Get current court selection
+ const courtDropdown = document.getElementById("court");
+ const selectedCourt = courtDropdown ? courtDropdown.value : null;
- html += "
";
+ if (!selectedCourt) {
+ // No court selected yet, show by default
+ return true;
}
- html += "
";
- }
- });
+ const conditionalReqs = partyGroup.conditional_requirements;
- return html;
- }
+ // Check if current court is in hidden_for_courts list
+ if (
+ conditionalReqs.hidden_for_courts &&
+ conditionalReqs.hidden_for_courts.includes(selectedCourt)
+ ) {
+ return false;
+ }
- shouldShowSection(partyGroup) {
- // If no conditional requirements defined, show by default
- if (!partyGroup.conditional_requirements) {
- return true;
- }
+ // Check if current court is in required_for_courts list (should show)
+ if (
+ conditionalReqs.required_for_courts &&
+ conditionalReqs.required_for_courts.includes(selectedCourt)
+ ) {
+ return true;
+ }
- // Get current court selection
- const courtDropdown = document.getElementById("court");
- const selectedCourt = courtDropdown ? courtDropdown.value : null;
+ // Check if current court is in optional_for_courts list (should show but optional)
+ if (
+ conditionalReqs.optional_for_courts &&
+ conditionalReqs.optional_for_courts.includes(selectedCourt)
+ ) {
+ return true;
+ }
- if (!selectedCourt) {
- // No court selected yet, show by default
- return true;
- }
+ // Check county-based requirements (extract county from court code)
+ if (
+ conditionalReqs.required_for_counties ||
+ conditionalReqs.optional_for_counties
+ ) {
+ const county = this.extractCountyFromCourt(selectedCourt);
- const conditionalReqs = partyGroup.conditional_requirements;
+ if (
+ conditionalReqs.required_for_counties &&
+ conditionalReqs.required_for_counties.includes(county)
+ ) {
+ return true;
+ }
- // Check if current court is in hidden_for_courts list
- if (
- conditionalReqs.hidden_for_courts &&
- conditionalReqs.hidden_for_courts.includes(selectedCourt)
- ) {
- return false;
- }
+ if (
+ conditionalReqs.optional_for_counties &&
+ conditionalReqs.optional_for_counties.includes(county)
+ ) {
+ return true;
+ }
+ }
- // Check if current court is in required_for_courts list (should show)
- if (
- conditionalReqs.required_for_courts &&
- conditionalReqs.required_for_courts.includes(selectedCourt)
- ) {
- return true;
- }
+ // For "Name Sought" section, hide by default for all other courts
+ if (
+ partyGroup.section_title &&
+ partyGroup.section_title.toLowerCase().includes("name sought")
+ ) {
+ return false;
+ }
- // Check if current court is in optional_for_courts list (should show but optional)
- if (
- conditionalReqs.optional_for_courts &&
- conditionalReqs.optional_for_courts.includes(selectedCourt)
- ) {
- return true;
- }
+ // If we have conditional requirements but the current court isn't explicitly listed,
+ // default to showing for "Petitioner" and hiding for other sections
+ if (
+ partyGroup.section_title &&
+ partyGroup.section_title.toLowerCase().includes("petitioner")
+ ) {
+ return true; // Show Petitioner by default unless explicitly hidden
+ }
- // Check county-based requirements (extract county from court code)
- if (
- conditionalReqs.required_for_counties ||
- conditionalReqs.optional_for_counties
- ) {
- const county = this.extractCountyFromCourt(selectedCourt);
-
- if (
- conditionalReqs.required_for_counties &&
- conditionalReqs.required_for_counties.includes(county)
- ) {
- return true;
- }
-
- if (
- conditionalReqs.optional_for_counties &&
- conditionalReqs.optional_for_counties.includes(county)
- ) {
- return true;
- }
+ // For all other sections with conditional requirements, hide by default
+ return false;
}
- // For "Name Sought" section, hide by default for all other courts
- if (
- partyGroup.section_title &&
- partyGroup.section_title.toLowerCase().includes("name sought")
- ) {
- return false;
- }
+ evaluateConditionalRequirement(partyGroup) {
+ // If no conditional requirements defined, use default required value
+ if (!partyGroup.conditional_requirements) {
+ return partyGroup.required || false;
+ }
- // If we have conditional requirements but the current court isn't explicitly listed,
- // default to showing for "Petitioner" and hiding for other sections
- if (
- partyGroup.section_title &&
- partyGroup.section_title.toLowerCase().includes("petitioner")
- ) {
- return true; // Show Petitioner by default unless explicitly hidden
- }
+ // Get current court selection
+ const courtDropdown = document.getElementById("court");
+ const selectedCourt = courtDropdown ? courtDropdown.value : null;
- // For all other sections with conditional requirements, hide by default
- return false;
- }
+ if (!selectedCourt) {
+ // No court selected yet, default to base required value
+ return partyGroup.required || false;
+ }
- evaluateConditionalRequirement(partyGroup) {
- // If no conditional requirements defined, use default required value
- if (!partyGroup.conditional_requirements) {
- return partyGroup.required || false;
- }
+ const conditionalReqs = partyGroup.conditional_requirements;
- // Get current court selection
- const courtDropdown = document.getElementById("court");
- const selectedCourt = courtDropdown ? courtDropdown.value : null;
+ // Check if current court is in required_for_courts list
+ if (
+ conditionalReqs.required_for_courts &&
+ conditionalReqs.required_for_courts.includes(selectedCourt)
+ ) {
+ return true;
+ }
- if (!selectedCourt) {
- // No court selected yet, default to base required value
- return partyGroup.required || false;
- }
+ // Check if current court is in optional_for_courts list
+ if (
+ conditionalReqs.optional_for_courts &&
+ conditionalReqs.optional_for_courts.includes(selectedCourt)
+ ) {
+ return false;
+ }
- const conditionalReqs = partyGroup.conditional_requirements;
+ // Check county-based requirements (extract county from court code)
+ if (
+ conditionalReqs.required_for_counties ||
+ conditionalReqs.optional_for_counties
+ ) {
+ const county = this.extractCountyFromCourt(selectedCourt);
- // Check if current court is in required_for_courts list
- if (
- conditionalReqs.required_for_courts &&
- conditionalReqs.required_for_courts.includes(selectedCourt)
- ) {
- return true;
- }
+ if (
+ conditionalReqs.required_for_counties &&
+ conditionalReqs.required_for_counties.includes(county)
+ ) {
+ return true;
+ }
- // Check if current court is in optional_for_courts list
- if (
- conditionalReqs.optional_for_courts &&
- conditionalReqs.optional_for_courts.includes(selectedCourt)
- ) {
- return false;
- }
+ if (
+ conditionalReqs.optional_for_counties &&
+ conditionalReqs.optional_for_counties.includes(county)
+ ) {
+ return false;
+ }
+ }
- // Check county-based requirements (extract county from court code)
- if (
- conditionalReqs.required_for_counties ||
- conditionalReqs.optional_for_counties
- ) {
- const county = this.extractCountyFromCourt(selectedCourt);
-
- if (
- conditionalReqs.required_for_counties &&
- conditionalReqs.required_for_counties.includes(county)
- ) {
- return true;
- }
-
- if (
- conditionalReqs.optional_for_counties &&
- conditionalReqs.optional_for_counties.includes(county)
- ) {
- return false;
- }
+ // Default to base required value if no specific rules match
+ return partyGroup.required || false;
}
- // Default to base required value if no specific rules match
- return partyGroup.required || false;
- }
+ extractCountyFromCourt(courtCode) {
+ // Handle court codes like "cook:cd1" -> "cook"
+ if (courtCode.includes(":")) {
+ return courtCode.split(":")[0];
+ }
- extractCountyFromCourt(courtCode) {
- // Handle court codes like "cook:cd1" -> "cook"
- if (courtCode.includes(":")) {
- return courtCode.split(":")[0];
+ // Handle direct county codes like "dupage", "kane"
+ return courtCode.toLowerCase();
}
- // Handle direct county codes like "dupage", "kane"
- return courtCode.toLowerCase();
- }
-
- renderServicesSection(sectionConfig) {
- const fields = sectionConfig.fields || [];
- let html = '';
+ renderServicesSection(sectionConfig) {
+ const fields = sectionConfig.fields || [];
+ let html = '
';
- fields.forEach((field) => {
- if (field.type === "checkbox") {
- html += `
+ fields.forEach((field) => {
+ if (field.type === "checkbox") {
+ html += `
`;
- }
- });
-
- html += "
";
- return html;
- }
+ }
+ });
- renderField(field) {
- if (!field || typeof field !== "object") {
- return '
Error: Invalid field configuration
';
+ html += "
";
+ return html;
}
- const columnClass = field.column_width || "col-12";
- const requiredAttr = field.required ? "required" : "";
- const placeholderAttr = field.placeholder
- ? `placeholder="${field.placeholder}"`
- : "";
- const fieldId = field.name || "unknown";
- const fieldName = field.name || "unknown";
-
- let inputHtml = "";
-
- switch (field.type) {
- case "text":
- inputHtml = ` `;
- break;
-
- case "textarea":
- inputHtml = ``;
- break;
-
- case "number":
- const minAttr = field.min ? `min="${field.min}"` : "";
- const maxAttr = field.max ? `max="${field.max}"` : "";
- const stepAttr = field.step ? `step="${field.step}"` : "";
- inputHtml = ` `;
- break;
-
- case "email":
- inputHtml = ` `;
- break;
-
- case "tel":
- inputHtml = ` `;
- break;
-
- case "us_state":
- const STATE_CHOICES = [
- ["", "Select a state"],
- ["AL", "Alabama"],
- ["AK", "Alaska"],
- ["AS", "American Samoa"],
- ["AZ", "Arizona"],
- ["AR", "Arkansas"],
- ["CA", "California"],
- ["CO", "Colorado"],
- ["CT", "Connecticut"],
- ["DE", "Delaware"],
- ["DC", "District of Columbia"],
- ["FL", "Florida"],
- ["GA", "Georgia"],
- ["GU", "Guam"],
- ["HI", "Hawaii"],
- ["ID", "Idaho"],
- ["IL", "Illinois"],
- ["IN", "Indiana"],
- ["IA", "Iowa"],
- ["KS", "Kansas"],
- ["KY", "Kentucky"],
- ["LA", "Louisiana"],
- ["ME", "Maine"],
- ["MD", "Maryland"],
- ["MA", "Massachusetts"],
- ["MI", "Michigan"],
- ["MN", "Minnesota"],
- ["MS", "Mississippi"],
- ["MO", "Missouri"],
- ["MT", "Montana"],
- ["NE", "Nebraska"],
- ["NV", "Nevada"],
- ["NH", "New Hampshire"],
- ["NJ", "New Jersey"],
- ["NM", "New Mexico"],
- ["NY", "New York"],
- ["NC", "North Carolina"],
- ["ND", "North Dakota"],
- ["MP", "Northern Mariana Islands"],
- ["OH", "Ohio"],
- ["OK", "Oklahoma"],
- ["OR", "Oregon"],
- ["PA", "Pennsylvania"],
- ["PR", "Puerto Rico"],
- ["RI", "Rhode Island"],
- ["SC", "South Carolina"],
- ["SD", "South Dakota"],
- ["TN", "Tennessee"],
- ["TX", "Texas"],
- ["UT", "Utah"],
- ["VT", "Vermont"],
- ["VA", "Virginia"],
- ["VI", "US Virgin Islands"],
- ["WA", "Washington"],
- ["WV", "West Virginia"],
- ["WI", "Wisconsin"],
- ["WY", "Wyoming"]
- ]
- inputHtml = ``
- for (let state of STATE_CHOICES) {
- inputHtml += `${state[1]} `
+ renderField(field) {
+ if (!field || typeof field !== "object") {
+ return 'Error: Invalid field configuration
';
}
- inputHtml += " "
- break;
- case "party_type_dropdown":
- // Create a dropdown for party types that will be populated via API
- inputHtml = `
+ const columnClass = field.column_width || "col-12";
+ const requiredAttr = field.required ? "required" : "";
+ const placeholderAttr = field.placeholder ?
+ `placeholder="${field.placeholder}"` :
+ "";
+ const fieldId = field.name || "unknown";
+ const fieldName = field.name || "unknown";
+
+ let inputHtml = "";
+
+ switch (field.type) {
+ case "text":
+ inputHtml = ` `;
+ break;
+
+ case "textarea":
+ inputHtml = ``;
+ break;
+
+ case "number":
+ const minAttr = field.min ? `min="${field.min}"` : "";
+ const maxAttr = field.max ? `max="${field.max}"` : "";
+ const stepAttr = field.step ? `step="${field.step}"` : "";
+ inputHtml = ` `;
+ break;
+
+ case "email":
+ inputHtml = ` `;
+ break;
+
+ case "tel":
+ inputHtml = ` `;
+ break;
+
+ case "us_state":
+ const STATE_CHOICES = [
+ ["", "Select a state"],
+ ["AL", "Alabama"],
+ ["AK", "Alaska"],
+ ["AS", "American Samoa"],
+ ["AZ", "Arizona"],
+ ["AR", "Arkansas"],
+ ["CA", "California"],
+ ["CO", "Colorado"],
+ ["CT", "Connecticut"],
+ ["DE", "Delaware"],
+ ["DC", "District of Columbia"],
+ ["FL", "Florida"],
+ ["GA", "Georgia"],
+ ["GU", "Guam"],
+ ["HI", "Hawaii"],
+ ["ID", "Idaho"],
+ ["IL", "Illinois"],
+ ["IN", "Indiana"],
+ ["IA", "Iowa"],
+ ["KS", "Kansas"],
+ ["KY", "Kentucky"],
+ ["LA", "Louisiana"],
+ ["ME", "Maine"],
+ ["MD", "Maryland"],
+ ["MA", "Massachusetts"],
+ ["MI", "Michigan"],
+ ["MN", "Minnesota"],
+ ["MS", "Mississippi"],
+ ["MO", "Missouri"],
+ ["MT", "Montana"],
+ ["NE", "Nebraska"],
+ ["NV", "Nevada"],
+ ["NH", "New Hampshire"],
+ ["NJ", "New Jersey"],
+ ["NM", "New Mexico"],
+ ["NY", "New York"],
+ ["NC", "North Carolina"],
+ ["ND", "North Dakota"],
+ ["MP", "Northern Mariana Islands"],
+ ["OH", "Ohio"],
+ ["OK", "Oklahoma"],
+ ["OR", "Oregon"],
+ ["PA", "Pennsylvania"],
+ ["PR", "Puerto Rico"],
+ ["RI", "Rhode Island"],
+ ["SC", "South Carolina"],
+ ["SD", "South Dakota"],
+ ["TN", "Tennessee"],
+ ["TX", "Texas"],
+ ["UT", "Utah"],
+ ["VT", "Vermont"],
+ ["VA", "Virginia"],
+ ["VI", "US Virgin Islands"],
+ ["WA", "Washington"],
+ ["WV", "West Virginia"],
+ ["WI", "Wisconsin"],
+ ["WY", "Wyoming"]
+ ]
+ inputHtml = ``
+ for (let state of STATE_CHOICES) {
+ inputHtml += `${state[1]} `
+ }
+ inputHtml += " "
+ break;
+
+ case "party_type_dropdown":
+ // Create a dropdown for party types that will be populated via API
+ inputHtml = `
Loading party types...
`;
- break;
+ break;
- default:
- inputHtml = ` `;
- }
+ default:
+ inputHtml = ` `;
+ }
- const helpText = field.help_text
- ? `${field.help_text}
`
- : "";
- const fieldLabel = field.label || field.name || "Field";
+ const helpText = field.help_text ?
+ `${field.help_text}
` :
+ "";
+ const fieldLabel = field.label || field.name || "Field";
- return `
+ return `
${fieldLabel}
${inputHtml}
${helpText}
`;
- }
-
- async loadPartyTypeDropdowns() {
- // Find all party type dropdowns in the rendered sections
- const partyTypeDropdowns = document.querySelectorAll(
- ".party-type-dropdown"
- );
-
- if (partyTypeDropdowns.length === 0) {
- return;
}
- partyTypeDropdowns.forEach(async (dropdown) => {
- const fieldId = dropdown.id;
- const apiEndpoint =
- dropdown.dataset.apiEndpoint || "/api/dropdowns/party-types/";
- let defaultValue = dropdown.dataset.defaultValue || "";
-
- // Check for saved case data first (highest priority)
- if (
- window.formValidation &&
- window.formValidation.restorationData &&
- window.formValidation.restorationData[fieldId]
- ) {
- defaultValue = window.formValidation.restorationData[fieldId];
- }
- // Check for restoration data from preserved form state
- else if (this.restorationData && this.restorationData[fieldId]) {
- defaultValue = this.restorationData[fieldId];
- }
- // Check for preserved form data
- else if (this.preservedFormData && this.preservedFormData[fieldId]) {
- defaultValue = this.preservedFormData[fieldId];
- }
-
- // Show loading spinner
- const loadingSpinner = document.getElementById(`loading-${fieldId}`);
- if (loadingSpinner) {
- loadingSpinner.style.display = "block";
- }
-
- try {
- // Get current form values for court and case_type
- const courtSelect = document.getElementById("court");
- const caseTypeSelect = document.getElementById("case_type");
-
- const court = courtSelect ? courtSelect.value : "";
- const caseType = caseTypeSelect ? caseTypeSelect.value : "";
+ async loadPartyTypeDropdowns() {
+ // Find all party type dropdowns in the rendered sections
+ const partyTypeDropdowns = document.querySelectorAll(
+ ".party-type-dropdown"
+ );
- if (!court || !caseType) {
- if (loadingSpinner) loadingSpinner.style.display = "none";
- return;
+ if (partyTypeDropdowns.length === 0) {
+ return;
}
- // Build API URL with parameters
- const result = await apiUtils.get(apiEndpoint, {court, "case_type": caseType, "jurisdiction": this.jurisdiction});
-
- if (result.success && result.data) {
- const partyTypes = result.data;
-
- // Clear existing options except the first one
- dropdown.innerHTML = 'Select Party Type ';
-
- // If no saved data, try to find intelligent default from API response
- if (!defaultValue) {
- // Find the parent section title
- const partySection = dropdown.closest(".party-section");
- if (partySection) {
- const titleElement = partySection.querySelector(".party-title");
- if (titleElement) {
- const sectionTitle = titleElement.textContent
- .toLowerCase()
- .replace(/\s*\*\s*$/, "")
- .replace(/\s*\(optional\)\s*$/i, "")
- .trim();
-
- // Find party type that matches the section title
- const matchingPartyType = partyTypes.find((partyType) => {
- const partyName = partyType.name.toLowerCase();
- return (
- partyName.includes(sectionTitle) ||
- sectionTitle.includes(partyName.split(" ")[0]) ||
- (sectionTitle === "name sought" &&
- partyName.includes("name")) ||
- (sectionTitle === "petitioner" &&
- partyName.includes("petitioner")) ||
- (sectionTitle === "defendant" &&
- partyName.includes("defendant")) ||
- (sectionTitle === "plaintiff" &&
- partyName.includes("plaintiff")) ||
- (sectionTitle === "respondent" &&
- partyName.includes("respondent"))
- );
- });
+ partyTypeDropdowns.forEach(async (dropdown) => {
+ const fieldId = dropdown.id;
+ const apiEndpoint =
+ dropdown.dataset.apiEndpoint || "/api/dropdowns/party-types/";
+ let defaultValue = dropdown.dataset.defaultValue || "";
- if (matchingPartyType) {
- defaultValue = matchingPartyType.code;
- }
- }
+ // Check for saved case data first (highest priority)
+ if (
+ window.formValidation &&
+ window.formValidation.restorationData &&
+ window.formValidation.restorationData[fieldId]
+ ) {
+ defaultValue = window.formValidation.restorationData[fieldId];
}
- }
-
- // Add all party type options
- let addedCount = 0;
- partyTypes.forEach((partyType) => {
- const option = document.createElement("option");
- option.value = partyType.code;
- option.textContent = partyType.name;
- // Set as selected if this is the default value
- if (partyType.code === defaultValue) {
- option.selected = true;
+ // Check for restoration data from preserved form state
+ else if (this.restorationData && this.restorationData[fieldId]) {
+ defaultValue = this.restorationData[fieldId];
+ }
+ // Check for preserved form data
+ else if (this.preservedFormData && this.preservedFormData[fieldId]) {
+ defaultValue = this.preservedFormData[fieldId];
}
- dropdown.appendChild(option);
- addedCount++;
- });
- } else {
- console.error(
- "Failed to load party types:",
- result.error || "Unknown error",
- result
- );
-
- // Add error option
- dropdown.innerHTML =
- 'Error loading party types ';
- }
- } catch (error) {
- console.error(`Error loading party types for ${fieldId}:`, error);
-
- // Add error option
- dropdown.innerHTML =
- 'Error loading party types ';
- } finally {
- // Hide loading spinner
- if (loadingSpinner) {
- loadingSpinner.style.display = "none";
- }
- }
- });
- }
-
- showDynamicSections() {
- if (this.dynamicSections) {
- this.dynamicSections.style.display = "block";
-
- // Also verify content was rendered
- const partiesContent = this.partiesContainer
- ? this.partiesContainer.innerHTML.trim()
- : "";
- const servicesContent = this.servicesContainer
- ? this.servicesContainer.innerHTML.trim()
- : "";
-
- if (partiesContent.length === 0 && servicesContent.length === 0) {
- console.warn("Dynamic sections shown but no content rendered!");
- }
- } else {
- }
- }
+ // Show loading spinner
+ const loadingSpinner = document.getElementById(`loading-${fieldId}`);
+ if (loadingSpinner) {
+ loadingSpinner.style.display = "block";
+ }
- hideDynamicSections() {
- if (this.dynamicSections) {
- this.dynamicSections.style.display = "none";
- } else {
- }
+ try {
+ // Get current form values for court and case_type
+ const courtSelect = document.getElementById("court");
+ const caseTypeSelect = document.getElementById("case_type");
- // Add a timeout to prevent immediate clearing race conditions
- setTimeout(() => {
- this.clearContainers();
- }, 50);
- }
-
- // Add a method to preserve current form state during dropdown changes
- preserveCurrentState() {
- if (this.currentCaseType) {
- const currentConfig = this.findCaseTypeConfig(this.currentCaseType);
- if (currentConfig) {
- // Store current form values
- const formData = {};
- const dynamicFields = this.getAllDynamicFieldNames();
- dynamicFields.forEach((fieldName) => {
- const field = document.querySelector(`[name="${fieldName}"]`);
- if (field) {
- if (field.type === "checkbox") {
- formData[fieldName] = field.checked;
- } else {
- formData[fieldName] = field.value;
+ const court = courtSelect ? courtSelect.value : "";
+ const caseType = caseTypeSelect ? caseTypeSelect.value : "";
+
+ if (!court || !caseType) {
+ if (loadingSpinner) loadingSpinner.style.display = "none";
+ return;
+ }
+
+ // Build API URL with parameters
+ const result = await apiUtils.get(apiEndpoint, {
+ court,
+ "case_type": caseType,
+ "jurisdiction": this.jurisdiction
+ });
+
+ if (result.success && result.data) {
+ const partyTypes = result.data;
+
+ // Clear existing options except the first one
+ dropdown.innerHTML = 'Select Party Type ';
+
+ // If no saved data, try to find intelligent default from API response
+ if (!defaultValue) {
+ // Find the parent section title
+ const partySection = dropdown.closest(".party-section");
+ if (partySection) {
+ const titleElement = partySection.querySelector(".party-title");
+ if (titleElement) {
+ const sectionTitle = titleElement.textContent
+ .toLowerCase()
+ .replace(/\s*\*\s*$/, "")
+ .replace(/\s*\(optional\)\s*$/i, "")
+ .trim();
+
+ // Find party type that matches the section title
+ const matchingPartyType = partyTypes.find((partyType) => {
+ const partyName = partyType.name.toLowerCase();
+ return (
+ partyName.includes(sectionTitle) ||
+ sectionTitle.includes(partyName.split(" ")[0]) ||
+ (sectionTitle === "name sought" &&
+ partyName.includes("name")) ||
+ (sectionTitle === "petitioner" &&
+ partyName.includes("petitioner")) ||
+ (sectionTitle === "defendant" &&
+ partyName.includes("defendant")) ||
+ (sectionTitle === "plaintiff" &&
+ partyName.includes("plaintiff")) ||
+ (sectionTitle === "respondent" &&
+ partyName.includes("respondent"))
+ );
+ });
+
+ if (matchingPartyType) {
+ defaultValue = matchingPartyType.code;
+ }
+ }
+ }
+ }
+
+ // Add all party type options
+ let addedCount = 0;
+ partyTypes.forEach((partyType) => {
+ const option = document.createElement("option");
+ option.value = partyType.code;
+ option.textContent = partyType.name;
+ // Set as selected if this is the default value
+ if (partyType.code === defaultValue) {
+ option.selected = true;
+ }
+
+ dropdown.appendChild(option);
+ addedCount++;
+ });
+ } else {
+ console.error(
+ "Failed to load party types:",
+ result.error || "Unknown error",
+ result
+ );
+
+ // Add error option
+ dropdown.innerHTML =
+ 'Error loading party types ';
+ }
+ } catch (error) {
+ console.error(`Error loading party types for ${fieldId}:`, error);
+
+ // Add error option
+ dropdown.innerHTML =
+ 'Error loading party types ';
+ } finally {
+ // Hide loading spinner
+ if (loadingSpinner) {
+ loadingSpinner.style.display = "none";
+ }
}
- }
});
- this.preservedFormData = formData;
- this.preservedCaseType = this.currentCaseType;
- }
}
- }
-
- // Method to restore preserved state
- restorePreservedState() {
- if (
- this.preservedFormData &&
- this.preservedCaseType === this.currentCaseType
- ) {
- setTimeout(() => {
- Object.keys(this.preservedFormData).forEach((fieldName) => {
- const field = document.querySelector(`[name="${fieldName}"]`);
- if (field) {
- if (field.type === "checkbox") {
- field.checked = this.preservedFormData[fieldName];
- } else {
- field.value = this.preservedFormData[fieldName];
+
+ showDynamicSections() {
+ if (this.dynamicSections) {
+ this.dynamicSections.style.display = "block";
+
+ // Also verify content was rendered
+ const partiesContent = this.partiesContainer ?
+ this.partiesContainer.innerHTML.trim() :
+ "";
+ const servicesContent = this.servicesContainer ?
+ this.servicesContainer.innerHTML.trim() :
+ "";
+
+ if (partiesContent.length === 0 && servicesContent.length === 0) {
+ console.warn("Dynamic sections shown but no content rendered!");
}
- }
- });
- // Clear preserved data after restoration
- this.preservedFormData = null;
- this.preservedCaseType = null;
- }, 100);
+ } else {}
}
- }
- clearContainers() {
- if (this.caseInfoContainer) {
- this.caseInfoContainer.innerHTML = "";
- }
- if (this.partiesContainer) {
- this.partiesContainer.innerHTML = "";
+ hideDynamicSections() {
+ if (this.dynamicSections) {
+ this.dynamicSections.style.display = "none";
+ } else {}
+
+ // Add a timeout to prevent immediate clearing race conditions
+ setTimeout(() => {
+ this.clearContainers();
+ }, 50);
}
- if (this.partiesHeader) {
- this.partiesHeader.style.display = "none";
+
+ // Add a method to preserve current form state during dropdown changes
+ preserveCurrentState() {
+ if (this.currentCaseType) {
+ const currentConfig = this.findCaseTypeConfig(this.currentCaseType);
+ if (currentConfig) {
+ // Store current form values
+ const formData = {};
+ const dynamicFields = this.getAllDynamicFieldNames();
+ dynamicFields.forEach((fieldName) => {
+ const field = document.querySelector(`[name="${fieldName}"]`);
+ if (field) {
+ if (field.type === "checkbox") {
+ formData[fieldName] = field.checked;
+ } else {
+ formData[fieldName] = field.value;
+ }
+ }
+ });
+ this.preservedFormData = formData;
+ this.preservedCaseType = this.currentCaseType;
+ }
+ }
}
- if (this.servicesContainer) {
- this.servicesContainer.innerHTML = "";
+
+ // Method to restore preserved state
+ restorePreservedState() {
+ if (
+ this.preservedFormData &&
+ this.preservedCaseType === this.currentCaseType
+ ) {
+ setTimeout(() => {
+ Object.keys(this.preservedFormData).forEach((fieldName) => {
+ const field = document.querySelector(`[name="${fieldName}"]`);
+ if (field) {
+ if (field.type === "checkbox") {
+ field.checked = this.preservedFormData[fieldName];
+ } else {
+ field.value = this.preservedFormData[fieldName];
+ }
+ }
+ });
+ // Clear preserved data after restoration
+ this.preservedFormData = null;
+ this.preservedCaseType = null;
+ }, 100);
+ }
}
- }
-
- updateFormValidation() {
- // Re-initialize form validation to include dynamically added fields
- if (window.FormValidation && window.formValidation) {
- // Find all required fields in the form
- const form = document.querySelector("#expertForm");
- if (form) {
- window.formValidation.requiredFields =
- form.querySelectorAll("[required]");
-
- // Populate dynamic fields if restoration data is available
- if (window.formValidation.restorationData) {
- setTimeout(() => {
- this.populateRenderedFields(window.formValidation.restorationData);
- }, 100);
+
+ clearContainers() {
+ if (this.caseInfoContainer) {
+ this.caseInfoContainer.innerHTML = "";
+ }
+ if (this.partiesContainer) {
+ this.partiesContainer.innerHTML = "";
+ }
+ if (this.partiesHeader) {
+ this.partiesHeader.style.display = "none";
+ }
+ if (this.servicesContainer) {
+ this.servicesContainer.innerHTML = "";
}
- }
}
- }
-
- populateRenderedFields(data) {
- // Get all dynamic fields that were just rendered
- const dynamicFields = this.getAllDynamicFieldNames();
-
- let fieldsPopulated = 0;
-
- dynamicFields.forEach((key) => {
- if (data[key]) {
- const field = document.querySelector(`[name="${key}"]`);
- if (field) {
- if (field.type === "checkbox") {
- field.checked = Array.isArray(data[key])
- ? data[key].includes(field.value)
- : data[key] === field.value;
- } else if (field.classList.contains("party-type-dropdown")) {
- // For party type dropdowns, we may need to wait for options to load
- const setDropdownValue = () => {
- // Check if the option exists
- const option = field.querySelector(
- `option[value="${data[key]}"]`
- );
- if (option) {
- field.value = data[key];
-
- // Add visual validation feedback after successful population
- field.classList.remove("is-invalid");
- field.classList.add("is-valid");
- } else if (field.options.length <= 1) {
- // Options haven't loaded yet, wait a bit longer
- setTimeout(setDropdownValue, 500);
- }
- };
- setDropdownValue();
- } else {
- field.value = data[key];
- }
- fieldsPopulated++;
-
- // Add visual validation feedback (but not for dropdowns until they're populated)
- if (
- field.value &&
- field.value.trim() &&
- !field.classList.contains("party-type-dropdown")
- ) {
- field.classList.remove("is-invalid");
- field.classList.add("is-valid");
- }
+
+ updateFormValidation() {
+ // Re-initialize form validation to include dynamically added fields
+ if (window.FormValidation && window.formValidation) {
+ // Find all required fields in the form
+ const form = document.querySelector("#expertForm");
+ if (form) {
+ window.formValidation.requiredFields =
+ form.querySelectorAll("[required]");
+
+ // Populate dynamic fields if restoration data is available
+ if (window.formValidation.restorationData) {
+ setTimeout(() => {
+ this.populateRenderedFields(window.formValidation.restorationData);
+ }, 100);
+ }
+ }
}
- }
- });
+ }
- return fieldsPopulated;
- }
+ populateRenderedFields(data) {
+ // Get all dynamic fields that were just rendered
+ const dynamicFields = this.getAllDynamicFieldNames();
- getAllDynamicFieldNames() {
- // Get all field names from currently rendered dynamic content
- const allFields = [];
+ let fieldsPopulated = 0;
+
+ dynamicFields.forEach((key) => {
+ if (data[key]) {
+ const field = document.querySelector(`[name="${key}"]`);
+ if (field) {
+ if (field.type === "checkbox") {
+ field.checked = Array.isArray(data[key]) ?
+ data[key].includes(field.value) :
+ data[key] === field.value;
+ } else if (field.classList.contains("party-type-dropdown")) {
+ // For party type dropdowns, we may need to wait for options to load
+ const setDropdownValue = () => {
+ // Check if the option exists
+ const option = field.querySelector(
+ `option[value="${data[key]}"]`
+ );
+ if (option) {
+ field.value = data[key];
+
+ // Add visual validation feedback after successful population
+ field.classList.remove("is-invalid");
+ field.classList.add("is-valid");
+ } else if (field.options.length <= 1) {
+ // Options haven't loaded yet, wait a bit longer
+ setTimeout(setDropdownValue, 500);
+ }
+ };
+ setDropdownValue();
+ } else {
+ field.value = data[key];
+ }
+ fieldsPopulated++;
+
+ // Add visual validation feedback (but not for dropdowns until they're populated)
+ if (
+ field.value &&
+ field.value.trim() &&
+ !field.classList.contains("party-type-dropdown")
+ ) {
+ field.classList.remove("is-invalid");
+ field.classList.add("is-valid");
+ }
+ }
+ }
+ });
- if (this.caseInfoContainer) {
- const fields = this.caseInfoContainer.querySelectorAll("[name]");
- fields.forEach((field) => allFields.push(field.name));
+ return fieldsPopulated;
}
- if (this.partiesContainer) {
- const fields = this.partiesContainer.querySelectorAll("[name]");
- fields.forEach((field) => allFields.push(field.name));
- }
+ getAllDynamicFieldNames() {
+ // Get all field names from currently rendered dynamic content
+ const allFields = [];
- if (this.servicesContainer) {
- const fields = this.servicesContainer.querySelectorAll("[name]");
- fields.forEach((field) => allFields.push(field.name));
- }
+ if (this.caseInfoContainer) {
+ const fields = this.caseInfoContainer.querySelectorAll("[name]");
+ fields.forEach((field) => allFields.push(field.name));
+ }
+
+ if (this.partiesContainer) {
+ const fields = this.partiesContainer.querySelectorAll("[name]");
+ fields.forEach((field) => allFields.push(field.name));
+ }
- return [...new Set(allFields)]; // Remove duplicates
- }
+ if (this.servicesContainer) {
+ const fields = this.servicesContainer.querySelectorAll("[name]");
+ fields.forEach((field) => allFields.push(field.name));
+ }
- // Method to be called from form validation when restoration data is available
- restoreDynamicFieldData(data) {
- if (!data || Object.keys(data).length === 0) {
- return;
+ return [...new Set(allFields)]; // Remove duplicates
}
- // Store the data for later restoration
- this.restorationData = data;
+ // Method to be called from form validation when restoration data is available
+ restoreDynamicFieldData(data) {
+ if (!data || Object.keys(data).length === 0) {
+ return;
+ }
+
+ // Store the data for later restoration
+ this.restorationData = data;
- // If dynamic sections are already visible, populate immediately
- if (
- this.dynamicSections &&
- this.dynamicSections.style.display === "block"
- ) {
- this.populateRenderedFields(data);
+ // If dynamic sections are already visible, populate immediately
+ if (
+ this.dynamicSections &&
+ this.dynamicSections.style.display === "block"
+ ) {
+ this.populateRenderedFields(data);
+ }
}
- }
}
// Initialize when DOM is loaded
function initializeDynamicFormSections() {
- if (!window.dynamicFormSections) {
- window.dynamicFormSections = new DynamicFormSections();
- }
+ if (!window.dynamicFormSections) {
+ window.dynamicFormSections = new DynamicFormSections();
+ }
}
// Try multiple initialization approaches
if (document.readyState === "loading") {
- // DOM is still loading
- document.addEventListener("DOMContentLoaded", initializeDynamicFormSections);
+ // DOM is still loading
+ document.addEventListener("DOMContentLoaded", initializeDynamicFormSections);
} else {
- // DOM is already loaded
- initializeDynamicFormSections();
+ // DOM is already loaded
+ initializeDynamicFormSections();
}
// Also make the class available globally for manual instantiation
@@ -1302,5 +1304,5 @@ window.DynamicFormSections = DynamicFormSections;
// Export for use in other modules
if (typeof module !== "undefined" && module.exports) {
- module.exports = DynamicFormSections;
-}
+ module.exports = DynamicFormSections;
+}
\ No newline at end of file
diff --git a/efile_app/efile/static/js/expert-form-main.js b/efile_app/efile/static/js/expert-form-main.js
index 323710c..dbe6e2f 100644
--- a/efile_app/efile/static/js/expert-form-main.js
+++ b/efile_app/efile/static/js/expert-form-main.js
@@ -11,29 +11,29 @@ class ExpertForm {
async init() {
if (this.initialized) return;
-
+
try {
// Initialize cascading dropdowns first (they load user profile)
this.cascadingDropdowns = new CascadingDropdowns();
await this.cascadingDropdowns.init();
-
+
// Make cascading dropdowns globally accessible for jurisdiction switching
window.cascadingDropdowns = this.cascadingDropdowns;
-
+
// Initialize form validation
this.formValidation = new FormValidation();
-
+
// Make form validation available globally for dynamic sections
window.formValidation = this.formValidation;
-
+
if (window.caseData) {
this.formValidation.populateForm(window.caseData);
}
-
+
// Note: Auto-save removed - drafts are now only saved when user clicks "Save Draft" button
-
+
this.initialized = true;
-
+
} catch (error) {
console.error('Error initializing ExpertForm:', error);
this.showError(gettext('There was an error loading the form. Please refresh the page.'));
@@ -42,10 +42,10 @@ class ExpertForm {
// setupAutoSave() method removed - drafts are now only saved when user clicks "Save Draft" button
// This prevents automatic saving and gives users full control over when drafts are saved
-
+
hasFormContent(data) {
// Check if form has meaningful content beyond empty strings
- return Object.values(data).some(value =>
+ return Object.values(data).some(value =>
value && value.toString().trim() !== ''
);
}
@@ -54,7 +54,7 @@ class ExpertForm {
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.innerHTML = ` ${message}`;
-
+
const container = document.querySelector('.form-container');
if (container) {
container.insertBefore(errorDiv, container.firstChild);
@@ -103,4 +103,4 @@ document.addEventListener('DOMContentLoaded', function() {
// Make available globally for debugging/external access
window.ExpertForm = ExpertForm;
-window.getExpertFormInstance = () => expertFormInstance;
+window.getExpertFormInstance = () => expertFormInstance;
\ No newline at end of file
diff --git a/efile_app/efile/static/js/form-validation.js b/efile_app/efile/static/js/form-validation.js
index 07cef1f..1f282a9 100644
--- a/efile_app/efile/static/js/form-validation.js
+++ b/efile_app/efile/static/js/form-validation.js
@@ -1,250 +1,265 @@
-
/**
* FormValidation - Handles form validation and user interactions
* Features: Real-time validation, draft saving, submission handling, API caching
*/
class FormValidation {
- constructor() {
- this.form = document.querySelector("#expertForm");
+ constructor() {
+ this.form = document.querySelector("#expertForm");
- if (!this.form) {
- console.error("Expert form not found!");
- return;
- }
+ if (!this.form) {
+ console.error("Expert form not found!");
+ return;
+ }
- this.requiredFields = this.form.querySelectorAll("[required]");
+ this.requiredFields = this.form.querySelectorAll("[required]");
- this.init();
- }
+ this.init();
+ }
- init() {
- this.setupValidation();
- this.setupFormSubmission();
- this.restoreSessionData();
- }
+ init() {
+ this.setupValidation();
+ this.setupFormSubmission();
+ this.restoreSessionData();
+ }
- setupValidation() {
- // Add validation styling for required fields
- this.requiredFields.forEach((field) => {
- field.addEventListener("invalid", function () {
- this.classList.add("is-invalid");
- });
+ setupValidation() {
+ // Add validation styling for required fields
+ this.requiredFields.forEach((field) => {
+ field.addEventListener("invalid", function() {
+ this.classList.add("is-invalid");
+ });
+
+ field.addEventListener("input", function() {
+ if (this.validity.valid) {
+ this.classList.remove("is-invalid");
+ this.classList.add("is-valid");
+ }
+ });
+
+ // Add blur validation for immediate feedback
+ field.addEventListener("blur", function() {
+ if (this.value.trim() && this.validity.valid) {
+ this.classList.remove("is-invalid");
+ this.classList.add("is-valid");
+ } else if (this.value.trim() && !this.validity.valid) {
+ this.classList.add("is-invalid");
+ }
+ });
+ });
+ }
- field.addEventListener("input", function () {
- if (this.validity.valid) {
- this.classList.remove("is-invalid");
- this.classList.add("is-valid");
+ setupFormSubmission() {
+ if (!this.form) {
+ console.error("Cannot setup form submission - form not found");
+ return;
}
- });
-
- // Add blur validation for immediate feedback
- field.addEventListener("blur", function () {
- if (this.value.trim() && this.validity.valid) {
- this.classList.remove("is-invalid");
- this.classList.add("is-valid");
- } else if (this.value.trim() && !this.validity.valid) {
- this.classList.add("is-invalid");
+
+ // Add event listener to form submit
+ this.form.addEventListener("submit", (e) => {
+ this.handleFormSubmission(e);
+ });
+
+ // Also add event listener to the submit button directly as backup
+ const submitButton = this.form.querySelector('button[type="submit"]');
+ if (submitButton) {
+ submitButton.addEventListener("click", (e) => {
+ e.preventDefault();
+ this.handleFormSubmission(e);
+ });
}
- });
- });
- }
-
- setupFormSubmission() {
- if (!this.form) {
- console.error("Cannot setup form submission - form not found");
- return;
}
- // Add event listener to form submit
- this.form.addEventListener("submit", (e) => {
- this.handleFormSubmission(e);
- });
-
- // Also add event listener to the submit button directly as backup
- const submitButton = this.form.querySelector('button[type="submit"]');
- if (submitButton) {
- submitButton.addEventListener("click", (e) => {
- e.preventDefault();
- this.handleFormSubmission(e);
- });
- }
- }
-
- saveDraft() {
- const formData = this.collectFormData();
-
- // Check if form has meaningful content
- const hasContent = Object.values(formData).some(
- (value) => value && value.toString().trim() !== ""
- );
-
- if (!hasContent) {
- this.showNotification(
- "Please fill out some form fields before saving a draft.",
- "info"
- );
- return;
- }
+ saveDraft() {
+ const formData = this.collectFormData();
- // Save to localStorage as backup
- const draftData = {
- data: formData,
- timestamp: new Date().toISOString(),
- savedBy: "user_action", // Indicate this was saved manually by user
- };
-
- localStorage.setItem("expertFormDraft", JSON.stringify(draftData));
-
- // TODO: Send to server when server-side draft functionality is implemented
-
- this.showNotification(
- "Draft saved successfully! You can return to this form later to continue.",
- "success"
- );
- }
-
- async handleFormSubmission(e) {
- e.preventDefault(); // Prevent default form submission
- e.stopPropagation(); // Stop event bubbling
- e.stopImmediatePropagation(); // Stop any other handlers
-
- // Refresh required fields list to include any dynamically added fields
- this.requiredFields = this.form.querySelectorAll("[required]");
-
- let isValid = true;
- const invalidFields = [];
-
- this.requiredFields.forEach((field) => {
- if (!field.validity.valid || !field.value.trim()) {
- isValid = false;
- field.classList.add("is-invalid");
- invalidFields.push(field.labels[0]?.textContent || field.name);
- } else {
- field.classList.remove("is-invalid");
- field.classList.add("is-valid");
- }
- });
-
- if (!isValid) {
- this.showValidationErrors(invalidFields);
- this.scrollToFirstError();
- return false;
- }
-
- // Collect form data and add friendly names
- const formData = this.collectFormData();
- const enhancedFormData = this.addFriendlyNames(formData);
- const currentJurisdiction = apiUtils.getCurrentJurisdiction();
-
- try {
- // Save case data to session via API
- await apiUtils.saveCaseData({data: enhancedFormData});
- window.location.replace(`/jurisdiction/${currentJurisdiction}/upload/`);
- } catch (error) {
- console.error("Network error:", error);
- this.showNotification(
- "Error saving case data. Please try again.",
- "error"
- );
- }
+ // Check if form has meaningful content
+ const hasContent = Object.values(formData).some(
+ (value) => value && value.toString().trim() !== ""
+ );
+
+ if (!hasContent) {
+ this.showNotification(
+ "Please fill out some form fields before saving a draft.",
+ "info"
+ );
+ return;
+ }
- return false;
- }
+ // Save to localStorage as backup
+ const draftData = {
+ data: formData,
+ timestamp: new Date().toISOString(),
+ savedBy: "user_action", // Indicate this was saved manually by user
+ };
- addFriendlyNames(formData) {
- const enhanced = { ...formData };
+ localStorage.setItem("expertFormDraft", JSON.stringify(draftData));
- // Add friendly names from dropdown text
- const dropdownMappings = [
- { field: "court", friendlyField: "court_name" },
- { field: "case_category", friendlyField: "case_category_name" },
- { field: "case_type", friendlyField: "case_type_name" },
- { field: "filing_type", friendlyField: "filing_type_name" },
- { field: "document_type", friendlyField: "document_type_name" },
- ];
+ // TODO: Send to server when server-side draft functionality is implemented
- dropdownMappings.forEach(({ field, friendlyField }) => {
- const dropdown = this.form.querySelector(`[name="${field}"]`);
+ this.showNotification(
+ "Draft saved successfully! You can return to this form later to continue.",
+ "success"
+ );
+ }
- if (dropdown && dropdown.value) {
- const selectedOption = dropdown.selectedOptions[0];
- if (
- selectedOption &&
- selectedOption.text &&
- selectedOption.text !== "Please select..."
- ) {
- // Clean up text by removing "(Recommended)" for court names
- let friendlyText = selectedOption.text;
- if (field === "court") {
- friendlyText = friendlyText
- .replace(/\s*\(Recommended\)\s*$/i, "")
- .trim();
- }
- enhanced[friendlyField] = friendlyText;
+ async handleFormSubmission(e) {
+ e.preventDefault(); // Prevent default form submission
+ e.stopPropagation(); // Stop event bubbling
+ e.stopImmediatePropagation(); // Stop any other handlers
+
+ // Refresh required fields list to include any dynamically added fields
+ this.requiredFields = this.form.querySelectorAll("[required]");
+
+ let isValid = true;
+ const invalidFields = [];
+
+ this.requiredFields.forEach((field) => {
+ if (!field.validity.valid || !field.value.trim()) {
+ isValid = false;
+ field.classList.add("is-invalid");
+ invalidFields.push(field.labels[0]?.textContent || field.name);
+ } else {
+ field.classList.remove("is-invalid");
+ field.classList.add("is-valid");
+ }
+ });
+
+ if (!isValid) {
+ this.showValidationErrors(invalidFields);
+ this.scrollToFirstError();
+ return false;
}
- }
- });
- return enhanced;
- }
+ // Collect form data and add friendly names
+ const formData = this.collectFormData();
+ const enhancedFormData = this.addFriendlyNames(formData);
+ const currentJurisdiction = apiUtils.getCurrentJurisdiction();
- collectFormData() {
- const formData = new FormData(this.form);
- const data = {};
+ try {
+ // Save case data to session via API
+ await apiUtils.saveCaseData({
+ data: enhancedFormData
+ });
+ window.location.replace(`/jurisdiction/${currentJurisdiction}/upload/`);
+ } catch (error) {
+ console.error("Network error:", error);
+ this.showNotification(
+ "Error saving case data. Please try again.",
+ "error"
+ );
+ }
- for (let [key, value] of formData.entries()) {
- if (data[key]) {
- // Handle multiple values (like checkboxes)
- if (Array.isArray(data[key])) {
- data[key].push(value);
- } else {
- data[key] = [data[key], value];
+ return false;
+ }
+
+ addFriendlyNames(formData) {
+ const enhanced = {
+ ...formData
+ };
+
+ // Add friendly names from dropdown text
+ const dropdownMappings = [{
+ field: "court",
+ friendlyField: "court_name"
+ }, {
+ field: "case_category",
+ friendlyField: "case_category_name"
+ }, {
+ field: "case_type",
+ friendlyField: "case_type_name"
+ }, {
+ field: "filing_type",
+ friendlyField: "filing_type_name"
+ }, {
+ field: "document_type",
+ friendlyField: "document_type_name"
+ }, ];
+
+ dropdownMappings.forEach(({
+ field,
+ friendlyField
+ }) => {
+ const dropdown = this.form.querySelector(`[name="${field}"]`);
+
+ if (dropdown && dropdown.value) {
+ const selectedOption = dropdown.selectedOptions[0];
+ if (
+ selectedOption &&
+ selectedOption.text &&
+ selectedOption.text !== "Please select..."
+ ) {
+ // Clean up text by removing "(Recommended)" for court names
+ let friendlyText = selectedOption.text;
+ if (field === "court") {
+ friendlyText = friendlyText
+ .replace(/\s*\(Recommended\)\s*$/i, "")
+ .trim();
+ }
+ enhanced[friendlyField] = friendlyText;
+ }
+ }
+ });
+
+ return enhanced;
+ }
+
+ collectFormData() {
+ const formData = new FormData(this.form);
+ const data = {};
+
+ for (let [key, value] of formData.entries()) {
+ if (data[key]) {
+ // Handle multiple values (like checkboxes)
+ if (Array.isArray(data[key])) {
+ data[key].push(value);
+ } else {
+ data[key] = [data[key], value];
+ }
+ } else {
+ data[key] = value;
+ }
}
- } else {
- data[key] = value;
- }
+
+ // Also collect disabled fields manually since FormData excludes them
+ const disabledFields = this.form.querySelectorAll('input[disabled], select[disabled], textarea[disabled]');
+ disabledFields.forEach(field => {
+ if (field.name && field.value) {
+ data[field.name] = field.value;
+ }
+ });
+
+ return data;
}
- // Also collect disabled fields manually since FormData excludes them
- const disabledFields = this.form.querySelectorAll('input[disabled], select[disabled], textarea[disabled]');
- disabledFields.forEach(field => {
- if (field.name && field.value) {
- data[field.name] = field.value;
- }
- });
-
- return data;
- }
-
- showValidationErrors(invalidFields) {
- const message =
- invalidFields.length > 1
- ? `Please fill in the following required fields: ${invalidFields.join(
+ showValidationErrors(invalidFields) {
+ const message =
+ invalidFields.length > 1 ?
+ `Please fill in the following required fields: ${invalidFields.join(
", "
- )}`
- : `Please fill in the required field: ${invalidFields[0]}`;
-
- this.showNotification(message, "error");
- }
-
- scrollToFirstError() {
- const firstError = this.form.querySelector(".is-invalid");
- if (firstError) {
- firstError.scrollIntoView({
- behavior: "smooth",
- block: "center",
- });
- firstError.focus();
+ )}` :
+ `Please fill in the required field: ${invalidFields[0]}`;
+
+ this.showNotification(message, "error");
+ }
+
+ scrollToFirstError() {
+ const firstError = this.form.querySelector(".is-invalid");
+ if (firstError) {
+ firstError.scrollIntoView({
+ behavior: "smooth",
+ block: "center",
+ });
+ firstError.focus();
+ }
}
- }
- showNotification(message, type = "info") {
- // Create notification element
- const notification = document.createElement("div");
- notification.className = `alert alert-${
+ showNotification(message, type = "info") {
+ // Create notification element
+ const notification = document.createElement("div");
+ notification.className = `alert alert-${
type === "error" ? "danger" : type
} notification-toast`;
- notification.style.cssText = `
+ notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
@@ -253,425 +268,439 @@ class FormValidation {
animation: slideIn 0.3s ease-out;
`;
- const icon =
- type === "success"
- ? "check-circle"
- : type === "error"
- ? "exclamation-circle"
- : "info-circle";
+ const icon =
+ type === "success" ?
+ "check-circle" :
+ type === "error" ?
+ "exclamation-circle" :
+ "info-circle";
- notification.innerHTML = `
+ notification.innerHTML = `
${message}
`;
- document.body.appendChild(notification);
-
- // Auto-remove after 5 seconds
- // setTimeout(() => {
- // if (notification.parentNode) {
- // notification.style.animation = "slideOut 0.3s ease-in";
- // setTimeout(() => {
- // notification.remove();
- // }, 300);
- // }
- // }, 5000);
-
- // Add click to dismiss
- notification.querySelector(".btn-close")?.addEventListener("click", () => {
- notification.remove();
- });
- }
-
- // Method to restore draft data
- restoreDraft() {
- const draft = localStorage.getItem("expertFormDraft");
- if (draft) {
- try {
- const draftObj = JSON.parse(draft);
- const { data, timestamp, savedBy } = draftObj;
- const age = Date.now() - new Date(timestamp).getTime();
-
- // Only restore if draft is less than 24 hours old
- if (age < 24 * 60 * 60 * 1000) {
- this.populateForm(data);
- const saveMethod =
- savedBy === "user_action" ? "manually saved" : "auto-saved";
- this.showNotification(
- gettext(`Draft restored from previous session (${saveMethod})`),
- "info"
- );
- } else {
- // Remove old draft
- localStorage.removeItem("expertFormDraft");
- }
- } catch (error) {
- console.warn("Could not restore draft:", error);
- localStorage.removeItem("expertFormDraft"); // Remove corrupted draft
- }
+ document.body.appendChild(notification);
+
+ // Auto-remove after 5 seconds
+ // setTimeout(() => {
+ // if (notification.parentNode) {
+ // notification.style.animation = "slideOut 0.3s ease-in";
+ // setTimeout(() => {
+ // notification.remove();
+ // }, 300);
+ // }
+ // }, 5000);
+
+ // Add click to dismiss
+ notification.querySelector(".btn-close")?.addEventListener("click", () => {
+ notification.remove();
+ });
}
- }
-
- populateForm(data) {
- // Store data for later use with dynamic fields
- this.restorationData = data;
-
- Object.keys(data).forEach((key) => {
- const fields = this.form.querySelectorAll(`[name="${key}"]`);
- if (fields.length == 1) {
- let field = fields[0];
- if (field.type === "checkbox" || field.type === "radio") {
- field.checked = Array.isArray(data[key])
- ? data[key].includes(field.value)
- : data[key] === field.value;
- if (field.type === "radio" && field.checked) {
- field.dispatchEvent(new Event("change", { bubbles: true }));
- }
- } else {
- field.value = Array.isArray(data[key]) ? data[key][0] : data[key];
- }
- // Trigger change event for dropdowns to update dependent fields
- if (field.tagName === "SELECT") {
- field.dispatchEvent(new Event("change", { bubbles: true }));
- }
- } else if (fields.length > 1) {
- for (let field of fields) {
- if (field.type === "radio") {
- field.checked = Array.isArray(data[key])
- ? data[key].includes(field.value)
- : data[key] === field.value;
- if (field.checked) {
- field.dispatchEvent(new Event("change", { bubbles: true }));
+ // Method to restore draft data
+ restoreDraft() {
+ const draft = localStorage.getItem("expertFormDraft");
+ if (draft) {
+ try {
+ const draftObj = JSON.parse(draft);
+ const {
+ data,
+ timestamp,
+ savedBy
+ } = draftObj;
+ const age = Date.now() - new Date(timestamp).getTime();
+
+ // Only restore if draft is less than 24 hours old
+ if (age < 24 * 60 * 60 * 1000) {
+ this.populateForm(data);
+ const saveMethod =
+ savedBy === "user_action" ? "manually saved" : "auto-saved";
+ this.showNotification(
+ gettext(`Draft restored from previous session (${saveMethod})`),
+ "info"
+ );
+ } else {
+ // Remove old draft
+ localStorage.removeItem("expertFormDraft");
+ }
+ } catch (error) {
+ console.warn("Could not restore draft:", error);
+ localStorage.removeItem("expertFormDraft"); // Remove corrupted draft
}
- }
}
- }
- });
-
- // For case type field, trigger dynamic sections immediately, then populate dynamic fields
- if (data.case_type && window.dynamicFormSections) {
- // First, pass the restoration data to dynamic form sections
- window.dynamicFormSections.restoreDynamicFieldData(data);
-
- // Give time for change event to propagate and trigger dynamic sections
- setTimeout(() => {
- window.dynamicFormSections.handleCaseTypeChange();
- // Then wait for dynamic fields to actually be rendered in the DOM
- this.waitForDynamicFieldsAndPopulate(data);
- }, 200);
}
- }
-
- async waitForDynamicFieldsAndPopulate(data) {
- // List of dynamic fields that we're waiting for
- const dynamicFields = [
- "petitioner_first_name",
- "petitioner_last_name",
- "new_first_name",
- "new_last_name",
- ];
-
- // Check if any of these fields exist in our data to restore
- const fieldsToRestore = dynamicFields.filter((field) => data[field]);
-
- if (fieldsToRestore.length === 0) {
- return;
+
+ populateForm(data) {
+ // Store data for later use with dynamic fields
+ this.restorationData = data;
+
+ Object.keys(data).forEach((key) => {
+ const fields = this.form.querySelectorAll(`[name="${key}"]`);
+ if (fields.length == 1) {
+ let field = fields[0];
+ if (field.type === "checkbox" || field.type === "radio") {
+ field.checked = Array.isArray(data[key]) ?
+ data[key].includes(field.value) :
+ data[key] === field.value;
+ if (field.type === "radio" && field.checked) {
+ field.dispatchEvent(new Event("change", {
+ bubbles: true
+ }));
+ }
+ } else {
+ field.value = Array.isArray(data[key]) ? data[key][0] : data[key];
+ }
+
+ // Trigger change event for dropdowns to update dependent fields
+ if (field.tagName === "SELECT") {
+ field.dispatchEvent(new Event("change", {
+ bubbles: true
+ }));
+ }
+ } else if (fields.length > 1) {
+ for (let field of fields) {
+ if (field.type === "radio") {
+ field.checked = Array.isArray(data[key]) ?
+ data[key].includes(field.value) :
+ data[key] === field.value;
+ if (field.checked) {
+ field.dispatchEvent(new Event("change", {
+ bubbles: true
+ }));
+ }
+ }
+ }
+ }
+ });
+
+ // For case type field, trigger dynamic sections immediately, then populate dynamic fields
+ if (data.case_type && window.dynamicFormSections) {
+ // First, pass the restoration data to dynamic form sections
+ window.dynamicFormSections.restoreDynamicFieldData(data);
+
+ // Give time for change event to propagate and trigger dynamic sections
+ setTimeout(() => {
+ window.dynamicFormSections.handleCaseTypeChange();
+ // Then wait for dynamic fields to actually be rendered in the DOM
+ this.waitForDynamicFieldsAndPopulate(data);
+ }, 200);
+ }
}
- // Wait up to 8 seconds for the fields to appear in the DOM (increased timeout)
- let attempts = 0;
- const maxAttempts = 80; // 8 seconds with 100ms intervals
-
- const checkInterval = setInterval(() => {
- attempts++;
-
- // Check if all required fields are now in the DOM
- const foundFields = fieldsToRestore.filter((fieldName) => {
- return this.form.querySelector(`[name="${fieldName}"]`) !== null;
- });
-
- // If all fields are found, or we've reached max attempts, populate what we can
- if (
- foundFields.length === fieldsToRestore.length ||
- attempts >= maxAttempts
- ) {
- clearInterval(checkInterval);
- this.populateDynamicFields(data);
- }
- }, 100);
- }
-
- populateDynamicFields(data) {
- // List of all possible dynamic fields that might be rendered after case type selection
- const dynamicFields = [
- "petitioner_first_name",
- "petitioner_last_name",
- "petitioner_address",
- "new_first_name",
- "new_last_name",
- // Add any other dynamic fields that might exist
- "petitioner_phone",
- "petitioner_email",
- "reason_for_change",
- ];
-
- let fieldsPopulated = 0;
- let fieldsNotFound = [];
-
- dynamicFields.forEach((key) => {
- if (data[key]) {
- const field = this.form.querySelector(`[name="${key}"]`);
- if (field) {
- // Handle different field types
- if (field.type === "checkbox" || field.type === "radio") {
- field.checked = Array.isArray(data[key])
- ? data[key].includes(field.value)
- : data[key] === field.value;
- } else {
- field.value = Array.isArray(data[key]) ? data[key][0] : data[key];
- }
- fieldsPopulated++;
-
- // Trigger validation styling if the field has content
- if (field.value && field.value.trim()) {
- field.classList.remove("is-invalid");
- field.classList.add("is-valid");
- }
- } else {
- fieldsNotFound.push(key);
+ async waitForDynamicFieldsAndPopulate(data) {
+ // List of dynamic fields that we're waiting for
+ const dynamicFields = [
+ "petitioner_first_name",
+ "petitioner_last_name",
+ "new_first_name",
+ "new_last_name",
+ ];
+
+ // Check if any of these fields exist in our data to restore
+ const fieldsToRestore = dynamicFields.filter((field) => data[field]);
+
+ if (fieldsToRestore.length === 0) {
+ return;
}
- }
- });
- // Show success notification if fields were populated
- if (fieldsPopulated > 0) {
- // this.showNotification(`Restored ${fieldsPopulated} name field(s) from saved data`, 'success');
- }
- }
-
- restoreSessionData() {
- // Check if case data is available from Django template context
- if (
- typeof window.caseData !== "undefined" &&
- window.caseData &&
- Object.keys(window.caseData).length > 0
- ) {
- // Wait for cascading dropdowns to initialize, then populate all dropdowns
- setTimeout(() => {
- this.populateDropdownsWithApiCalls(window.caseData);
- // this.showNotification('Restoring previous case data...', 'info');
- }, 2000); // Wait for initial dropdown system to load
- } else {
- // Fallback to draft restoration from localStorage
- this.restoreDraft();
+ // Wait up to 8 seconds for the fields to appear in the DOM (increased timeout)
+ let attempts = 0;
+ const maxAttempts = 80; // 8 seconds with 100ms intervals
+
+ const checkInterval = setInterval(() => {
+ attempts++;
+
+ // Check if all required fields are now in the DOM
+ const foundFields = fieldsToRestore.filter((fieldName) => {
+ return this.form.querySelector(`[name="${fieldName}"]`) !== null;
+ });
+
+ // If all fields are found, or we've reached max attempts, populate what we can
+ if (
+ foundFields.length === fieldsToRestore.length ||
+ attempts >= maxAttempts
+ ) {
+ clearInterval(checkInterval);
+ this.populateDynamicFields(data);
+ }
+ }, 100);
}
- }
-
- async populateDropdownsWithApiCalls(data) {
- // First populate all the non-dropdown form fields
- Object.keys(data).forEach((key) => {
- if (
- ![
- "court",
- "case_category",
- "case_type",
- "filing_type",
- "document_type",
- ].includes(key)
- ) {
- const field = this.form.querySelector(`[name="${key}"]`);
- if (field) {
- field.value = Array.isArray(data[key]) ? data[key][0] : data[key];
- }
- }
- });
-
- try {
- // Step 1: Load courts (should already be loaded, but ensure selection)
- if (data.court) {
- await this.waitForDropdownOptions("court");
- this.setDropdownValue("court", data.court);
- }
-
- // Step 2: Load case categories based on court
- if (data.court && data.case_category) {
- try {
- await this.loadCascadingDropdown(
- "court",
- data.court,
- "case_category"
- );
- this.setDropdownValue("case_category", data.case_category);
- } catch (error) {
- console.warn(
- "Failed to load case categories during restoration:",
- error
- );
- }
- }
- // Step 3: Load case types based on case category
- if (data.case_category && data.case_type) {
- try {
- await this.loadCascadingDropdown(
- "case_category",
- data.case_category,
- "case_type"
- );
- this.setDropdownValue("case_type", data.case_type);
- } catch (error) {
- console.warn("Failed to load case types during restoration:", error);
+ populateDynamicFields(data) {
+ // List of all possible dynamic fields that might be rendered after case type selection
+ const dynamicFields = [
+ "petitioner_first_name",
+ "petitioner_last_name",
+ "petitioner_address",
+ "new_first_name",
+ "new_last_name",
+ // Add any other dynamic fields that might exist
+ "petitioner_phone",
+ "petitioner_email",
+ "reason_for_change",
+ ];
+
+ let fieldsPopulated = 0;
+ let fieldsNotFound = [];
+
+ dynamicFields.forEach((key) => {
+ if (data[key]) {
+ const field = this.form.querySelector(`[name="${key}"]`);
+ if (field) {
+ // Handle different field types
+ if (field.type === "checkbox" || field.type === "radio") {
+ field.checked = Array.isArray(data[key]) ?
+ data[key].includes(field.value) :
+ data[key] === field.value;
+ } else {
+ field.value = Array.isArray(data[key]) ? data[key][0] : data[key];
+ }
+ fieldsPopulated++;
+
+ // Trigger validation styling if the field has content
+ if (field.value && field.value.trim()) {
+ field.classList.remove("is-invalid");
+ field.classList.add("is-valid");
+ }
+ } else {
+ fieldsNotFound.push(key);
+ }
+ }
+ });
+
+ // Show success notification if fields were populated
+ if (fieldsPopulated > 0) {
+ // this.showNotification(`Restored ${fieldsPopulated} name field(s) from saved data`, 'success');
}
- }
+ }
- // Step 4: Load filing types based on case type
- if (data.case_type && data.filing_type) {
- try {
- await this.loadCascadingDropdown(
- "case_type",
- data.case_type,
- "filing_type"
- );
- this.setDropdownValue("filing_type", data.filing_type);
- } catch (error) {
- console.warn(
- "Failed to load filing types during restoration:",
- error
- );
+ restoreSessionData() {
+ // Check if case data is available from Django template context
+ if (
+ typeof window.caseData !== "undefined" &&
+ window.caseData &&
+ Object.keys(window.caseData).length > 0
+ ) {
+ // Wait for cascading dropdowns to initialize, then populate all dropdowns
+ setTimeout(() => {
+ this.populateDropdownsWithApiCalls(window.caseData);
+ // this.showNotification('Restoring previous case data...', 'info');
+ }, 2000); // Wait for initial dropdown system to load
+ } else {
+ // Fallback to draft restoration from localStorage
+ this.restoreDraft();
}
- }
+ }
+
+ async populateDropdownsWithApiCalls(data) {
+ // First populate all the non-dropdown form fields
+ Object.keys(data).forEach((key) => {
+ if (
+ ![
+ "court",
+ "case_category",
+ "case_type",
+ "filing_type",
+ "document_type",
+ ].includes(key)
+ ) {
+ const field = this.form.querySelector(`[name="${key}"]`);
+ if (field) {
+ field.value = Array.isArray(data[key]) ? data[key][0] : data[key];
+ }
+ }
+ });
- // Step 5: Load document types based on filing type
- if (data.filing_type && data.document_type) {
try {
- await this.loadCascadingDropdown(
- "filing_type",
- data.filing_type,
- "document_type"
- );
- this.setDropdownValue("document_type", data.document_type);
+ // Step 1: Load courts (should already be loaded, but ensure selection)
+ if (data.court) {
+ await this.waitForDropdownOptions("court");
+ this.setDropdownValue("court", data.court);
+ }
+
+ // Step 2: Load case categories based on court
+ if (data.court && data.case_category) {
+ try {
+ await this.loadCascadingDropdown(
+ "court",
+ data.court,
+ "case_category"
+ );
+ this.setDropdownValue("case_category", data.case_category);
+ } catch (error) {
+ console.warn(
+ "Failed to load case categories during restoration:",
+ error
+ );
+ }
+ }
+
+ // Step 3: Load case types based on case category
+ if (data.case_category && data.case_type) {
+ try {
+ await this.loadCascadingDropdown(
+ "case_category",
+ data.case_category,
+ "case_type"
+ );
+ this.setDropdownValue("case_type", data.case_type);
+ } catch (error) {
+ console.warn("Failed to load case types during restoration:", error);
+ }
+ }
+
+ // Step 4: Load filing types based on case type
+ if (data.case_type && data.filing_type) {
+ try {
+ await this.loadCascadingDropdown(
+ "case_type",
+ data.case_type,
+ "filing_type"
+ );
+ this.setDropdownValue("filing_type", data.filing_type);
+ } catch (error) {
+ console.warn(
+ "Failed to load filing types during restoration:",
+ error
+ );
+ }
+ }
+
+ // Step 5: Load document types based on filing type
+ if (data.filing_type && data.document_type) {
+ try {
+ await this.loadCascadingDropdown(
+ "filing_type",
+ data.filing_type,
+ "document_type"
+ );
+ this.setDropdownValue("document_type", data.document_type);
+ } catch (error) {
+ console.warn(
+ "Failed to load document types during restoration:",
+ error
+ );
+ }
+ }
+
+ // Step 6: After all dropdowns are restored, populate dynamic fields
+ // Wait a bit for dynamic sections to be rendered, then populate dynamic fields
+ if (data.case_type) {
+ setTimeout(async () => {
+ await this.waitForDynamicFieldsAndPopulate(data);
+ }, 1000); // Give more time for dynamic sections to render
+ }
} catch (error) {
- console.warn(
- "Failed to load document types during restoration:",
- error
- );
+ console.error("Error during dropdown population:", error);
+ this.showNotification("Error restoring some dropdown values", "error");
}
- }
-
- // Step 6: After all dropdowns are restored, populate dynamic fields
- // Wait a bit for dynamic sections to be rendered, then populate dynamic fields
- if (data.case_type) {
- setTimeout(async () => {
- await this.waitForDynamicFieldsAndPopulate(data);
- }, 1000); // Give more time for dynamic sections to render
- }
- } catch (error) {
- console.error("Error during dropdown population:", error);
- this.showNotification("Error restoring some dropdown values", "error");
}
- }
- async waitForDropdownOptions(dropdownName, maxWaitMs = 5000) {
- const dropdown = this.form.querySelector(`[name="${dropdownName}"]`);
- if (!dropdown) {
- throw new Error(`Dropdown ${dropdownName} not found`);
- }
+ async waitForDropdownOptions(dropdownName, maxWaitMs = 5000) {
+ const dropdown = this.form.querySelector(`[name="${dropdownName}"]`);
+ if (!dropdown) {
+ throw new Error(`Dropdown ${dropdownName} not found`);
+ }
- const startTime = Date.now();
+ const startTime = Date.now();
- while (dropdown.options.length <= 1 && Date.now() - startTime < maxWaitMs) {
- await new Promise((resolve) => setTimeout(resolve, 500));
- }
- }
-
- async loadCascadingDropdown(
- parentDropdownName,
- parentValue,
- targetDropdownName
- ) {
- const parentDropdown = this.form.querySelector(
- `[name="${parentDropdownName}"]`
- );
- const targetDropdown = this.form.querySelector(
- `[name="${targetDropdownName}"]`
- );
-
- if (!parentDropdown || !targetDropdown) {
- throw new Error(
- `Dropdown not found: ${parentDropdownName} or ${targetDropdownName}`
- );
+ while (dropdown.options.length <= 1 && Date.now() - startTime < maxWaitMs) {
+ await new Promise((resolve) => setTimeout(resolve, 500));
+ }
}
- // Set parent dropdown value if not already set
- if (parentDropdown.value !== parentValue) {
- parentDropdown.value = parentValue;
- }
+ async loadCascadingDropdown(
+ parentDropdownName,
+ parentValue,
+ targetDropdownName
+ ) {
+ const parentDropdown = this.form.querySelector(
+ `[name="${parentDropdownName}"]`
+ );
+ const targetDropdown = this.form.querySelector(
+ `[name="${targetDropdownName}"]`
+ );
+
+ if (!parentDropdown || !targetDropdown) {
+ throw new Error(
+ `Dropdown not found: ${parentDropdownName} or ${targetDropdownName}`
+ );
+ }
- // Trigger change event to populate target dropdown
- const changeEvent = new Event("change", { bubbles: true });
- parentDropdown.dispatchEvent(changeEvent);
+ // Set parent dropdown value if not already set
+ if (parentDropdown.value !== parentValue) {
+ parentDropdown.value = parentValue;
+ }
- // Wait for target dropdown to be populated
- await this.waitForDropdownOptions(targetDropdownName, 10000);
- }
+ // Trigger change event to populate target dropdown
+ const changeEvent = new Event("change", {
+ bubbles: true
+ });
+ parentDropdown.dispatchEvent(changeEvent);
- setDropdownValue(dropdownName, value) {
- const dropdown = this.form.querySelector(`[name="${dropdownName}"]`);
- if (!dropdown) {
- console.warn(`Dropdown ${dropdownName} not found`);
- return;
+ // Wait for target dropdown to be populated
+ await this.waitForDropdownOptions(targetDropdownName, 10000);
}
- // Special handling for filing_type search dropdown
- if (dropdownName === "filing_type" && window.filingTypeSearch) {
- const option = dropdown.querySelector(`option[value="${value}"]`);
- if (option) {
- // Use the search dropdown's setValue method to properly update both the hidden select and the UI
- window.filingTypeSearch.setValue(value, false); // Don't trigger change event to avoid cascading
- } else {
- console.warn(`Filing type option not found: ${value}`);
- }
- return;
- }
+ setDropdownValue(dropdownName, value) {
+ const dropdown = this.form.querySelector(`[name="${dropdownName}"]`);
+ if (!dropdown) {
+ console.warn(`Dropdown ${dropdownName} not found`);
+ return;
+ }
- // Check if the option exists for regular dropdowns
- const option = dropdown.querySelector(`option[value="${value}"]`);
- if (option) {
- dropdown.value = value;
-
- // Special handling for case_type - trigger dynamic form sections
- if (dropdownName === "case_type" && value) {
- setTimeout(() => {
- if (window.dynamicFormSections) {
- // Pass restoration data to dynamic sections first
- if (this.restorationData) {
- window.dynamicFormSections.restoreDynamicFieldData(
- this.restorationData
- );
+ // Special handling for filing_type search dropdown
+ if (dropdownName === "filing_type" && window.filingTypeSearch) {
+ const option = dropdown.querySelector(`option[value="${value}"]`);
+ if (option) {
+ // Use the search dropdown's setValue method to properly update both the hidden select and the UI
+ window.filingTypeSearch.setValue(value, false); // Don't trigger change event to avoid cascading
+ } else {
+ console.warn(`Filing type option not found: ${value}`);
}
+ return;
+ }
- window.dynamicFormSections.handleCaseTypeChange();
- } else {
+ // Check if the option exists for regular dropdowns
+ const option = dropdown.querySelector(`option[value="${value}"]`);
+ if (option) {
+ dropdown.value = value;
+
+ // Special handling for case_type - trigger dynamic form sections
+ if (dropdownName === "case_type" && value) {
+ setTimeout(() => {
+ if (window.dynamicFormSections) {
+ // Pass restoration data to dynamic sections first
+ if (this.restorationData) {
+ window.dynamicFormSections.restoreDynamicFieldData(
+ this.restorationData
+ );
+ }
+
+ window.dynamicFormSections.handleCaseTypeChange();
+ } else {
+ console.warn(
+ "dynamicFormSections not available when setting case_type"
+ );
+ }
+ const changeEvent = new Event("change", {
+ bubbles: true
+ });
+ dropdown.dispatchEvent(changeEvent);
+ }, 300); // Give time for dropdown to settle
+ }
+ } else {
console.warn(
- "dynamicFormSections not available when setting case_type"
+ `Option with value "${value}" not found for ${dropdownName}`
);
- }
- const changeEvent = new Event("change", { bubbles: true });
- dropdown.dispatchEvent(changeEvent);
- }, 300); // Give time for dropdown to settle
- }
- } else {
- console.warn(
- `Option with value "${value}" not found for ${dropdownName}`
- );
- // List available options for debugging
- const options = Array.from(dropdown.options).map((opt) => ({
- value: opt.value,
- text: opt.text,
- }));
+ // List available options for debugging
+ const options = Array.from(dropdown.options).map((opt) => ({
+ value: opt.value,
+ text: opt.text,
+ }));
+ }
}
- }
}
// Add CSS for animations
@@ -694,7 +723,7 @@ document.head.appendChild(style);
// Export for module use or make globally available
if (typeof module !== "undefined" && module.exports) {
- module.exports = FormValidation;
+ module.exports = FormValidation;
} else {
- window.FormValidation = FormValidation;
-}
+ window.FormValidation = FormValidation;
+}
\ No newline at end of file
diff --git a/efile_app/efile/static/js/review.js b/efile_app/efile/static/js/review.js
index f7e39ef..8bb525f 100644
--- a/efile_app/efile/static/js/review.js
+++ b/efile_app/efile/static/js/review.js
@@ -5,338 +5,362 @@
// Configuration constants
const CONFIG = {
- VALIDATION: {
- EMAIL_REGEX: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
- ZIP_REGEX: /^\d{5}(-\d{4})?$/,
- PHONE_REGEX: /^\+?\d{7,15}$/
- },
- URLS: {
- UPLOAD_DATA: '/api/get-upload-data/',
- PROFILE: '/api/auth/profile/',
- PAYMENT_ACCOUNTS: '/api/payment-accounts/',
- TYLER_TOKEN: '/api/auth/tyler-token/',
- SUBMIT_FILING: '/api/submit-final-filing/',
- CASE_DATA: '/api/get-case-data/',
- }
+ VALIDATION: {
+ EMAIL_REGEX: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
+ ZIP_REGEX: /^\d{5}(-\d{4})?$/,
+ PHONE_REGEX: /^\+?\d{7,15}$/
+ },
+ URLS: {
+ UPLOAD_DATA: '/api/get-upload-data/',
+ PROFILE: '/api/auth/profile/',
+ PAYMENT_ACCOUNTS: '/api/payment-accounts/',
+ TYLER_TOKEN: '/api/auth/tyler-token/',
+ SUBMIT_FILING: '/api/submit-final-filing/',
+ CASE_DATA: '/api/get-case-data/',
+ }
};
// Utility functions
const Utils = {
- getElement(id) {
- return document.getElementById(id);
- },
-
- getElements(selector) {
- return document.querySelectorAll(selector);
- },
-
- parseJSON(elementId) {
- const element = this.getElement(elementId);
- return element ? JSON.parse(element.textContent) : {};
- },
-
- showElement(element) {
- if (element) element.style.display = "block";
- },
-
- hideElement(element) {
- if (element) element.style.display = "none";
- },
-
- // Validation helpers
- isValidEmail(email) {
- return email && CONFIG.VALIDATION.EMAIL_REGEX.test(email);
- },
-
- isValidZip(zip) {
- return zip && CONFIG.VALIDATION.ZIP_REGEX.test(zip);
- },
-
- isValidPhone(phone) {
- if (!phone) return false;
- const cleaned = phone.replace(/[\s()-\.]/g, "");
- return CONFIG.VALIDATION.PHONE_REGEX.test(cleaned);
- },
-
- // URL parameter helper
- getURLParam(param) {
- return new URLSearchParams(window.location.search).get(param);
- },
-
- cleanURL() {
- window.history.replaceState({}, document.title, window.location.pathname);
- }
+ getElement(id) {
+ return document.getElementById(id);
+ },
+
+ getElements(selector) {
+ return document.querySelectorAll(selector);
+ },
+
+ parseJSON(elementId) {
+ const element = this.getElement(elementId);
+ return element ? JSON.parse(element.textContent) : {};
+ },
+
+ showElement(element) {
+ if (element) element.style.display = "block";
+ },
+
+ hideElement(element) {
+ if (element) element.style.display = "none";
+ },
+
+ // Validation helpers
+ isValidEmail(email) {
+ return email && CONFIG.VALIDATION.EMAIL_REGEX.test(email);
+ },
+
+ isValidZip(zip) {
+ return zip && CONFIG.VALIDATION.ZIP_REGEX.test(zip);
+ },
+
+ isValidPhone(phone) {
+ if (!phone) return false;
+ const cleaned = phone.replace(/[\s()-\.]/g, "");
+ return CONFIG.VALIDATION.PHONE_REGEX.test(cleaned);
+ },
+
+ // URL parameter helper
+ getURLParam(param) {
+ return new URLSearchParams(window.location.search).get(param);
+ },
+
+ cleanURL() {
+ window.history.replaceState({}, document.title, window.location.pathname);
+ }
};
// Message handling
const Messages = {
- show(type, message) {
- const messageDiv = Utils.getElement(type === 'error' ? 'errorMessage' : 'successMessage');
- const textElement = Utils.getElement(type === 'error' ? 'errorText' : 'successText');
-
- if (messageDiv && textElement) {
- textElement.textContent = message;
- Utils.showElement(messageDiv);
- messageDiv.scrollIntoView({ behavior: "smooth", block: "center" });
- }
- },
+ show(type, message) {
+ const messageDiv = Utils.getElement(type === 'error' ? 'errorMessage' : 'successMessage');
+ const textElement = Utils.getElement(type === 'error' ? 'errorText' : 'successText');
+
+ if (messageDiv && textElement) {
+ textElement.textContent = message;
+ Utils.showElement(messageDiv);
+ messageDiv.scrollIntoView({
+ behavior: "smooth",
+ block: "center"
+ });
+ }
+ },
- showError(message) {
- this.show('error', message);
- },
+ showError(message) {
+ this.show('error', message);
+ },
- showSuccess(message) {
- this.show('success', message);
- },
+ showSuccess(message) {
+ this.show('success', message);
+ },
- hide() {
- Utils.hideElement(Utils.getElement('errorMessage'));
- Utils.hideElement(Utils.getElement('successMessage'));
- }
+ hide() {
+ Utils.hideElement(Utils.getElement('errorMessage'));
+ Utils.hideElement(Utils.getElement('successMessage'));
+ }
};
// Data management
const DataManager = {
- async fetchJSON(url, options = {}) {
- try {
- const response = await fetch(url, {
- headers: {
- 'Content-Type': 'application/json',
- 'X-CSRFToken': apiUtils.getCSRFToken(),
- ...options.headers
- },
- ...options
- });
- return response.ok ? await response.json() : null;
- } catch (error) {
- console.error(`Fetch error for ${url}:`, error);
- return null;
- }
- },
+ async fetchJSON(url, options = {}) {
+ try {
+ const response = await fetch(url, {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': apiUtils.getCSRFToken(),
+ ...options.headers
+ },
+ ...options
+ });
+ return response.ok ? await response.json() : null;
+ } catch (error) {
+ console.error(`Fetch error for ${url}:`, error);
+ return null;
+ }
+ },
- async getCaseData() {
- return Utils.parseJSON('case-data');
- },
+ async getCaseData() {
+ return Utils.parseJSON('case-data');
+ },
- getFriendlyNames() {
- return Utils.parseJSON('friendly-names');
- }
+ getFriendlyNames() {
+ return Utils.parseJSON('friendly-names');
+ }
};
// UI Field management
const FieldManager = {
- // Consolidated field setting logic
- setFieldValue(fieldPrefix, value, displayValue = value) {
- const input = Utils.getElement(`${fieldPrefix}Input`);
- const text = Utils.getElement(`${fieldPrefix}Text`);
-
- if (input) input.value = value || "";
- if (text) text.textContent = displayValue || "";
- },
-
- getFieldValue(fieldPrefix) {
- const input = Utils.getElement(`${fieldPrefix}Input`);
- const text = Utils.getElement(`${fieldPrefix}Text`);
- const inputVal = input?.value?.trim();
- const textVal = text?.textContent?.trim();
- return (inputVal && inputVal.length > 0) ? inputVal : textVal;
- },
-
- toggleEdit(inputId, button) {
- const input = Utils.getElement(inputId);
- const text = Utils.getElement(inputId.replace("Input", "Text"));
- if (!input || !text || !button) return;
-
- const isEditing = input.style.display !== "none";
-
- if (!isEditing) {
- this.startEditing(input, text, button, inputId);
- } else {
- this.saveField(input, text, button, inputId);
+ // Consolidated field setting logic
+ setFieldValue(fieldPrefix, value, displayValue = value) {
+ const input = Utils.getElement(`${fieldPrefix}Input`);
+ const text = Utils.getElement(`${fieldPrefix}Text`);
+
+ if (input) input.value = value || "";
+ if (text) text.textContent = displayValue || "";
+ },
+
+ getFieldValue(fieldPrefix) {
+ const input = Utils.getElement(`${fieldPrefix}Input`);
+ const text = Utils.getElement(`${fieldPrefix}Text`);
+ const inputVal = input?.value?.trim();
+ const textVal = text?.textContent?.trim();
+ return (inputVal && inputVal.length > 0) ? inputVal : textVal;
+ },
+
+ toggleEdit(inputId, button) {
+ const input = Utils.getElement(inputId);
+ const text = Utils.getElement(inputId.replace("Input", "Text"));
+ if (!input || !text || !button) return;
+
+ const isEditing = input.style.display !== "none";
+
+ if (!isEditing) {
+ this.startEditing(input, text, button, inputId);
+ } else {
+ this.saveField(input, text, button, inputId);
+ }
+ },
+
+ startEditing(input, text, button, inputId) {
+ Utils.hideElement(text);
+ Utils.showElement(input);
+ input.focus();
+ input.select();
+ button.textContent = "Save";
+ button.onclick = () => this.saveField(input, text, button, inputId);
+ },
+
+ saveField(input, text, button, inputId) {
+ text.textContent = input.value.trim();
+ Utils.hideElement(input);
+ Utils.showElement(text);
+ button.textContent = "Edit";
+ button.onclick = () => this.toggleEdit(inputId, button);
}
- },
-
- startEditing(input, text, button, inputId) {
- Utils.hideElement(text);
- Utils.showElement(input);
- input.focus();
- input.select();
- button.textContent = "Save";
- button.onclick = () => this.saveField(input, text, button, inputId);
- },
-
- saveField(input, text, button, inputId) {
- text.textContent = input.value.trim();
- Utils.hideElement(input);
- Utils.showElement(text);
- button.textContent = "Edit";
- button.onclick = () => this.toggleEdit(inputId, button);
- }
};
// Form validation
const FormValidator = {
- validateUserData(userData) {
- const { fullName, address, city, state, zip, email, phone } = userData;
-
- const requiredFields = [
- { value: fullName, name: 'Name' },
- { value: address, name: 'Address Line 1' },
- { value: city, name: 'City' },
- { value: state, name: 'State' },
- { value: zip, name: 'ZIP Code' },
- { value: email, name: 'Email' },
- { value: phone, name: 'Phone' }
- ];
-
- // Check required fields
- for (const field of requiredFields) {
- if (!field.value) {
- return `${field.name} is required.`;
- }
- }
+ validateUserData(userData) {
+ const {
+ fullName,
+ address,
+ city,
+ state,
+ zip,
+ email,
+ phone
+ } = userData;
+
+ const requiredFields = [{
+ value: fullName,
+ name: 'Name'
+ }, {
+ value: address,
+ name: 'Address Line 1'
+ }, {
+ value: city,
+ name: 'City'
+ }, {
+ value: state,
+ name: 'State'
+ }, {
+ value: zip,
+ name: 'ZIP Code'
+ }, {
+ value: email,
+ name: 'Email'
+ }, {
+ value: phone,
+ name: 'Phone'
+ }];
+
+ // Check required fields
+ for (const field of requiredFields) {
+ if (!field.value) {
+ return `${field.name} is required.`;
+ }
+ }
- // Validate formats
- if (!Utils.isValidZip(zip)) {
- return "Please enter a valid ZIP code (e.g. 60601 or 60601-1234).";
- }
-
- if (!Utils.isValidPhone(phone)) {
- return "Please enter a valid phone number.";
- }
-
- if (!Utils.isValidEmail(email)) {
- return "Please provide a valid email address.";
- }
+ // Validate formats
+ if (!Utils.isValidZip(zip)) {
+ return "Please enter a valid ZIP code (e.g. 60601 or 60601-1234).";
+ }
+
+ if (!Utils.isValidPhone(phone)) {
+ return "Please enter a valid phone number.";
+ }
+
+ if (!Utils.isValidEmail(email)) {
+ return "Please provide a valid email address.";
+ }
- return null; // No validation errors
- }
+ return null; // No validation errors
+ }
};
// API handlers
const APIHandlers = {
- async fetchPartyType() {
- const caseData = await DataManager.getCaseData();
-
- if (!caseData.court || !caseData.case_type) return;
-
- const params = {
- jurisdiction: apiUtils.getCurrentJurisdiction(),
- court: caseData.court,
- case_type: caseData.case_type,
- existing_case: caseData.existing_case || 'no'
- };
-
- const result = await apiUtils.getPartyTypes(params);
-
- if (result?.success) {
- console.log('Party types received:', result.party_types);
- console.log('Selected party type:', result.selected_party_type);
- } else {
- console.error('Party type fetch failed:', result?.error);
- }
- },
-
- async loadUserInfo() {
- const params = new URLSearchParams({
- jurisdiction: apiUtils.getCurrentJurisdiction(),
- });
- const data = await DataManager.fetchJSON(`${CONFIG.URLS.PROFILE}?${params}`);
-
- if (data?.success && data.data) {
- const profile = data.data;
- const fullName = [profile.first_name, profile.last_name].filter(n => n).join(" ");
-
- // Set all user fields
- const fields = [
- ['userName', fullName],
- ['userAddressLine1', profile.address],
- ['userAddressLine2', profile.address_line2],
- ['userCity', profile.city],
- ['userState', profile.state],
- ['userZip', profile.zip],
- ['userEmail', profile.email],
- ['userPhone', profile.phone]
- ];
-
- fields.forEach(([prefix, value]) => FieldManager.setFieldValue(prefix, value));
- } else {
- // Set empty values for all fields on failure
- ['userName', 'userAddressLine1', 'userAddressLine2', 'userCity', 'userState', 'userZip', 'userEmail', 'userPhone']
- .forEach(prefix => FieldManager.setFieldValue(prefix, "", "Please provide"));
- }
- },
+ async fetchPartyType() {
+ const caseData = await DataManager.getCaseData();
- async loadUploadData() {
- const data = await apiUtils.getUploadData();
- if (data) {
- UIUpdater.updateDocumentsSection(data);
- }
- },
-
- async loadPaymentAccounts() {
- const params = new URLSearchParams({
- jurisdiction: apiUtils.getCurrentJurisdiction(),
- });
- const result = await DataManager.fetchJSON(`${CONFIG.URLS.PAYMENT_ACCOUNTS}?${params}`);
-
- if (result?.success && result.data) {
- UIUpdater.updatePaymentMethodsSection(result.data);
- } else {
- UIUpdater.showAddNewPaymentMethod();
+ if (!caseData.court || !caseData.case_type) return;
+
+ const params = {
+ jurisdiction: apiUtils.getCurrentJurisdiction(),
+ court: caseData.court,
+ case_type: caseData.case_type,
+ existing_case: caseData.existing_case || 'no'
+ };
+
+ const result = await apiUtils.getPartyTypes(params);
+
+ if (result?.success) {
+ console.log('Party types received:', result.party_types);
+ console.log('Selected party type:', result.selected_party_type);
+ } else {
+ console.error('Party type fetch failed:', result?.error);
+ }
+ },
+
+ async loadUserInfo() {
+ const params = new URLSearchParams({
+ jurisdiction: apiUtils.getCurrentJurisdiction(),
+ });
+ const data = await DataManager.fetchJSON(`${CONFIG.URLS.PROFILE}?${params}`);
+
+ if (data?.success && data.data) {
+ const profile = data.data;
+ const fullName = [profile.first_name, profile.last_name].filter(n => n).join(" ");
+
+ // Set all user fields
+ const fields = [
+ ['userName', fullName],
+ ['userAddressLine1', profile.address],
+ ['userAddressLine2', profile.address_line2],
+ ['userCity', profile.city],
+ ['userState', profile.state],
+ ['userZip', profile.zip],
+ ['userEmail', profile.email],
+ ['userPhone', profile.phone]
+ ];
+
+ fields.forEach(([prefix, value]) => FieldManager.setFieldValue(prefix, value));
+ } else {
+ // Set empty values for all fields on failure
+ ['userName', 'userAddressLine1', 'userAddressLine2', 'userCity', 'userState', 'userZip', 'userEmail', 'userPhone']
+ .forEach(prefix => FieldManager.setFieldValue(prefix, "", "Please provide"));
+ }
+ },
+
+ async loadUploadData() {
+ const data = await apiUtils.getUploadData();
+ if (data) {
+ UIUpdater.updateDocumentsSection(data);
+ }
+ },
+
+ async loadPaymentAccounts() {
+ const params = new URLSearchParams({
+ jurisdiction: apiUtils.getCurrentJurisdiction(),
+ });
+ const result = await DataManager.fetchJSON(`${CONFIG.URLS.PAYMENT_ACCOUNTS}?${params}`);
+
+ if (result?.success && result.data) {
+ UIUpdater.updatePaymentMethodsSection(result.data);
+ } else {
+ UIUpdater.showAddNewPaymentMethod();
+ }
}
- }
};
// UI updaters
const UIUpdater = {
- updateCaseInfo(caseData, friendlyNames) {
- const caseTypeEl = Utils.getElement('caseTypeValue');
- const courtEl = Utils.getElement('courtValue');
+ updateCaseInfo(caseData, friendlyNames) {
+ const caseTypeEl = Utils.getElement('caseTypeValue');
+ const courtEl = Utils.getElement('courtValue');
- if (caseTypeEl) {
- caseTypeEl.textContent = friendlyNames.case_type || caseData.case_type_name || caseData.case_type || "Not specified";
- }
- if (courtEl) {
- courtEl.textContent = friendlyNames.court || caseData.court_name || caseData.court || "Not specified";
- }
- },
-
- updateDocumentsSection(uploadData) {
- const container = Utils.getElement('documentsContainer');
- if (!container) return;
-
- let html = "";
-
- // Lead document
- if (uploadData.files?.lead) {
- const docName = uploadData.files.lead.name.includes("Name Change") ? "Name Change Form" : "Lead Document";
- html += this.createDocumentHTML(docName, uploadData.files.lead.name, 'lead', true);
- } else {
- html += `1. Lead Document (required) No document found
`;
- }
+ if (caseTypeEl) {
+ caseTypeEl.textContent = friendlyNames.case_type || caseData.case_type_name || caseData.case_type || "Not specified";
+ }
+ if (courtEl) {
+ courtEl.textContent = friendlyNames.court || caseData.court_name || caseData.court || "Not specified";
+ }
+ },
+
+ updateDocumentsSection(uploadData) {
+ const container = Utils.getElement('documentsContainer');
+ if (!container) return;
- // Supporting documents
- if (uploadData.files?.supporting?.length > 0) {
- html += '2. Fee Waiver (optional) File or Files
';
-
- uploadData.files.supporting.forEach(file => {
- html += `
+ let html = "";
+
+ // Lead document
+ if (uploadData.files?.lead) {
+ const docName = uploadData.files.lead.name.includes("Name Change") ? "Name Change Form" : "Lead Document";
+ html += this.createDocumentHTML(docName, uploadData.files.lead.name, 'lead', true);
+ } else {
+ html += `
1. Lead Document (required) No document found
`;
+ }
+
+ // Supporting documents
+ if (uploadData.files?.supporting?.length > 0) {
+ html += '
2. Fee Waiver (optional) File or Files
';
+
+ uploadData.files.supporting.forEach(file => {
+ html += `
${file.name}
Change
`;
- });
-
- html += '
';
- }
+ });
- container.innerHTML = html;
- },
+ html += '
';
+ }
+
+ container.innerHTML = html;
+ },
- createDocumentHTML(title, filename, type, required = false) {
- return `
+ createDocumentHTML(title, filename, type, required = false) {
+ return `
1. ${title} ${required ? '(required)' : ''}
File or Files
@@ -347,32 +371,32 @@ const UIUpdater = {
`;
- },
-
- updatePaymentMethodsSection(paymentAccounts) {
- const container = Utils.getElement('paymentMethodsContainer');
- if (!container || !paymentAccounts?.length) {
- return this.showAddNewPaymentMethod();
- }
+ },
- let html = '
';
-
- let hasMultipleWaivers = paymentAccounts.filter((account) => account.paymentAccountTypeCode === "WV").length > 1;
- paymentAccounts.forEach((account, index) => {
- const isDefault = index === 0;
- const cardType = account.cardType?.value || "Card";
- const cardLast4 = account.cardLast4 || "****";
- let paymentText = `${cardType} ending in ${cardLast4}`;
-
- if (account.paymentAccountTypeCode === "WV") {
- if (hasMultipleWaivers) {
- paymentText = `Payment Waiver (named "${account.accountName}")`;
- } else {
- paymentText = 'Payment Waiver';
+ updatePaymentMethodsSection(paymentAccounts) {
+ const container = Utils.getElement('paymentMethodsContainer');
+ if (!container || !paymentAccounts?.length) {
+ return this.showAddNewPaymentMethod();
}
- }
- html += `
+ let html = '
';
+
+ let hasMultipleWaivers = paymentAccounts.filter((account) => account.paymentAccountTypeCode === "WV").length > 1;
+ paymentAccounts.forEach((account, index) => {
+ const isDefault = index === 0;
+ const cardType = account.cardType?.value || "Card";
+ const cardLast4 = account.cardLast4 || "****";
+ let paymentText = `${cardType} ending in ${cardLast4}`;
+
+ if (account.paymentAccountTypeCode === "WV") {
+ if (hasMultipleWaivers) {
+ paymentText = `Payment Waiver (named "${account.accountName}")`;
+ } else {
+ paymentText = 'Payment Waiver';
+ }
+ }
+
+ html += `
`;
- });
+ });
- html += `
+ html += `
Add New Payment Method
`;
- container.innerHTML = html;
- },
+ container.innerHTML = html;
+ },
+
+ showAddNewPaymentMethod() {
+ const container = Utils.getElement('paymentMethodsContainer');
+ if (!container) return;
- showAddNewPaymentMethod() {
- const container = Utils.getElement('paymentMethodsContainer');
- if (!container) return;
-
- container.innerHTML = `
+ container.innerHTML = `
No payment methods found. Please add a payment method to continue.
@@ -411,380 +435,390 @@ const UIUpdater = {
Add Payment Method
`;
- }
+ }
};
// Payment handling
const PaymentHandler = {
- async addNewPaymentMethod() {
- try {
- const params = new URLSearchParams({
- jurisdiction: apiUtils.getCurrentJurisdiction(),
- });
- const authData = await DataManager.fetchJSON(`${CONFIG.URLS.TYLER_TOKEN}?${params}`);
-
- if (!authData?.success || !authData.data?.tyler_token) {
- Messages.showError("Authentication failed. Please try again.");
- return;
- }
-
- this.redirectToPaymentForm(authData.data);
- } catch (error) {
- console.log("Create payment error: %o", error);
- Messages.showError("Failed to create payment method. Please try again.");
- }
- },
-
- redirectToPaymentForm(authData) {
- const form = document.createElement("form");
- form.method = "post";
-
- const jurisdiction = authData.state || apiUtils.getCurrentJurisdiction();
- // TODO: we should revisit this hardcoded value in the future too
- form.action = Utils.parseJSON('new-toga-url');
-
- let dateStr = new Date().toDateString();
- const fields = [
- ['account_name', `Payment Account made on ${dateStr}`],
- ['global', 'false'],
- ['type_code', 'CC'],
- ['tyler_info', authData.tyler_token],
- ['original_url', `${window.location.origin}/jurisdiction/${jurisdiction}/review/?payment_status=success`],
- ['error_url', `${window.location.origin}/jurisdiction/${jurisdiction}/review/?payment_status=failure`]
- ];
-
- fields.forEach(([name, value]) => {
- const input = document.createElement("input");
- input.type = "hidden";
- input.name = name;
- input.value = value;
- form.appendChild(input);
- });
-
- document.body.appendChild(form);
- form.submit();
- },
-
- handleCallback() {
- const status = Utils.getURLParam('payment_status');
-
- if (status === 'success') {
- Messages.showSuccess(gettext("Payment method added successfully!"));
- setTimeout(() => APIHandlers.loadPaymentAccounts(), 1000);
- Utils.cleanURL();
- } else if (status === 'failure') {
- Messages.showError(gettext("Failed to add payment method. Please try again."));
- Utils.cleanURL();
+ async addNewPaymentMethod() {
+ try {
+ const params = new URLSearchParams({
+ jurisdiction: apiUtils.getCurrentJurisdiction(),
+ });
+ const authData = await DataManager.fetchJSON(`${CONFIG.URLS.TYLER_TOKEN}?${params}`);
+
+ if (!authData?.success || !authData.data?.tyler_token) {
+ Messages.showError("Authentication failed. Please try again.");
+ return;
+ }
+
+ this.redirectToPaymentForm(authData.data);
+ } catch (error) {
+ console.log("Create payment error: %o", error);
+ Messages.showError("Failed to create payment method. Please try again.");
+ }
+ },
+
+ redirectToPaymentForm(authData) {
+ const form = document.createElement("form");
+ form.method = "post";
+
+ const jurisdiction = authData.state || apiUtils.getCurrentJurisdiction();
+ // TODO: we should revisit this hardcoded value in the future too
+ form.action = Utils.parseJSON('new-toga-url');
+
+ let dateStr = new Date().toDateString();
+ const fields = [
+ ['account_name', `Payment Account made on ${dateStr}`],
+ ['global', 'false'],
+ ['type_code', 'CC'],
+ ['tyler_info', authData.tyler_token],
+ ['original_url', `${window.location.origin}/jurisdiction/${jurisdiction}/review/?payment_status=success`],
+ ['error_url', `${window.location.origin}/jurisdiction/${jurisdiction}/review/?payment_status=failure`]
+ ];
+
+ fields.forEach(([name, value]) => {
+ const input = document.createElement("input");
+ input.type = "hidden";
+ input.name = name;
+ input.value = value;
+ form.appendChild(input);
+ });
+
+ document.body.appendChild(form);
+ form.submit();
+ },
+
+ handleCallback() {
+ const status = Utils.getURLParam('payment_status');
+
+ if (status === 'success') {
+ Messages.showSuccess(gettext("Payment method added successfully!"));
+ setTimeout(() => APIHandlers.loadPaymentAccounts(), 1000);
+ Utils.cleanURL();
+ } else if (status === 'failure') {
+ Messages.showError(gettext("Failed to add payment method. Please try again."));
+ Utils.cleanURL();
+ }
}
- }
};
// Navigation
const Navigation = {
- goBack() {
- window.location.href = `/jurisdiction/${apiUtils.getCurrentJurisdiction()}/upload`;
- },
-
- changeDocument(type) {
- const jurisdiction = apiUtils.getCurrentJurisdiction();
- if (type === "lead") {
- // TODO: still save all of the existing stuff?
- window.location.href = `/jurisdiction/${jurisdiction}/upload_first`;
- } else {
- window.location.href = `/jurisdiction/${jurisdiction}/upload`;
+ goBack() {
+ window.location.href = `/jurisdiction/${apiUtils.getCurrentJurisdiction()}/upload`;
+ },
+
+ changeDocument(type) {
+ const jurisdiction = apiUtils.getCurrentJurisdiction();
+ if (type === "lead") {
+ // TODO: still save all of the existing stuff?
+ window.location.href = `/jurisdiction/${jurisdiction}/upload_first`;
+ } else {
+ window.location.href = `/jurisdiction/${jurisdiction}/upload`;
+ }
}
- }
};
// Filing submission
const FilingHandler = {
- async submitFiling() {
- const userData = this.collectUserData();
- const validationError = FormValidator.validateUserData(userData);
-
- if (validationError) {
- Messages.showError(validationError);
- return;
- }
+ async submitFiling() {
+ const userData = this.collectUserData();
+ const validationError = FormValidator.validateUserData(userData);
- const selectedPaymentMethod = document.querySelector('input[name="paymentMethod"]:checked');
- if (!selectedPaymentMethod) {
- Messages.showError(gettext("Please select a payment method to continue."));
- return;
- }
+ if (validationError) {
+ Messages.showError(validationError);
+ return;
+ }
- this.setSubmissionState(true);
- Messages.hide();
+ const selectedPaymentMethod = document.querySelector('input[name="paymentMethod"]:checked');
+ if (!selectedPaymentMethod) {
+ Messages.showError(gettext("Please select a payment method to continue."));
+ return;
+ }
- try {
- const result = await this.processSubmission(userData, selectedPaymentMethod.value);
- this.handleSubmissionResult(result);
- } catch (error) {
- console.log("Error on submission: %o", error)
- Messages.showError(gexttext("An unexpected error occurred. Please try again."));
- this.setSubmissionState(false);
- }
- },
-
- collectUserData() {
- return {
- fullName: FieldManager.getFieldValue('userName'),
- address: FieldManager.getFieldValue('userAddressLine1'),
- addressLine2: FieldManager.getFieldValue('userAddressLine2'),
- city: FieldManager.getFieldValue('userCity'),
- state: FieldManager.getFieldValue('userState'),
- zip: FieldManager.getFieldValue('userZip'),
- email: FieldManager.getFieldValue('userEmail'),
- phone: FieldManager.getFieldValue('userPhone')
- };
- },
-
- setSubmissionState(isSubmitting) {
- const submitButton = Utils.getElement('submitButton');
- const loadingSpinner = Utils.getElement('loadingSpinner');
-
- if (submitButton) submitButton.disabled = isSubmitting;
- if (loadingSpinner) loadingSpinner.style.display = isSubmitting ? "block" : "none";
- },
-
- async processSubmission(userData, paymentAccountID) {
- let [caseData, uploadData] = await Promise.all([
- DataManager.fetchJSON(CONFIG.URLS.CASE_DATA),
- DataManager.fetchJSON(CONFIG.URLS.UPLOAD_DATA)
- ]);
-
- caseData = caseData.data.case_data;
-
- const efilingData = this.buildEFilingData(userData, caseData, uploadData, paymentAccountID);
-
- return await DataManager.fetchJSON(CONFIG.URLS.SUBMIT_FILING, {
- method: 'POST',
- body: JSON.stringify({
- efile_data: efilingData,
- confirm_submission: true,
- payment_account_id: paymentAccountID
- })
- });
- },
-
- buildEFilingData(userData, caseData, uploadData, paymentAccountID) {
- const nameParts = userData.fullName.split(" ");
- const firstName = nameParts[0] || "";
- const lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : "";
- const middleName = nameParts.length > 2 ? nameParts.slice(1, -1).join(" ") : "";
-
- const partyType = caseData.determined_party_type || caseData.petitioner_party_type || caseData.party_type;
-
- if (!partyType) {
- throw new Error('Party type could not be determined. This is required for eFiling.');
- }
+ this.setSubmissionState(true);
+ Messages.hide();
- // Build user object
- const mainUser = {
- mobile_number: userData.phone,
- phone_number: userData.phone,
- address: {
- address: userData.address,
- unit: userData.addressLine2,
- city: userData.city,
- state: userData.state,
- zip: userData.zip,
- country: "US"
- },
- email: userData.email,
- party_type: partyType,
- date_of_birth: "",
- is_form_filler: true,
- name: { first: firstName, middle: middleName, last: lastName, suffix: "" },
- is_new: true
- };
-
- const users = [mainUser];
-
- // Add second user if needed for name changes
- if (caseData.new_name_party_type) {
- users.push({
- ...mainUser,
- party_type: caseData.new_name_party_type,
- name: {
- first: caseData.new_first_name || firstName,
- middle: caseData.new_middle_name || middleName,
- last: caseData.new_last_name || lastName,
- suffix: caseData.new_suffix || ""
- }
- });
- }
+ try {
+ const result = await this.processSubmission(userData, selectedPaymentMethod.value);
+ this.handleSubmissionResult(result);
+ } catch (error) {
+ console.log("Error on submission: %o", error)
+ Messages.showError(gexttext("An unexpected error occurred. Please try again."));
+ this.setSubmissionState(false);
+ }
+ },
+
+ collectUserData() {
+ return {
+ fullName: FieldManager.getFieldValue('userName'),
+ address: FieldManager.getFieldValue('userAddressLine1'),
+ addressLine2: FieldManager.getFieldValue('userAddressLine2'),
+ city: FieldManager.getFieldValue('userCity'),
+ state: FieldManager.getFieldValue('userState'),
+ zip: FieldManager.getFieldValue('userZip'),
+ email: FieldManager.getFieldValue('userEmail'),
+ phone: FieldManager.getFieldValue('userPhone')
+ };
+ },
+
+ setSubmissionState(isSubmitting) {
+ const submitButton = Utils.getElement('submitButton');
+ const loadingSpinner = Utils.getElement('loadingSpinner');
+
+ if (submitButton) submitButton.disabled = isSubmitting;
+ if (loadingSpinner) loadingSpinner.style.display = isSubmitting ? "block" : "none";
+ },
+
+ async processSubmission(userData, paymentAccountID) {
+ let [caseData, uploadData] = await Promise.all([
+ DataManager.fetchJSON(CONFIG.URLS.CASE_DATA),
+ DataManager.fetchJSON(CONFIG.URLS.UPLOAD_DATA)
+ ]);
+
+ caseData = caseData.data.case_data;
+
+ const efilingData = this.buildEFilingData(userData, caseData, uploadData, paymentAccountID);
+
+ return await DataManager.fetchJSON(CONFIG.URLS.SUBMIT_FILING, {
+ method: 'POST',
+ body: JSON.stringify({
+ efile_data: efilingData,
+ confirm_submission: true,
+ payment_account_id: paymentAccountID
+ })
+ });
+ },
+
+ buildEFilingData(userData, caseData, uploadData, paymentAccountID) {
+ const nameParts = userData.fullName.split(" ");
+ const firstName = nameParts[0] || "";
+ const lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : "";
+ const middleName = nameParts.length > 2 ? nameParts.slice(1, -1).join(" ") : "";
+
+ const partyType = caseData.determined_party_type || caseData.petitioner_party_type || caseData.party_type;
+
+ if (!partyType) {
+ throw new Error('Party type could not be determined. This is required for eFiling.');
+ }
- // Add second user if needed for name changes
- if (caseData.respondent_name_party_type) {
- users.push({
- party_type: caseData.respondent_name_party_type,
- name: {
- first: caseData.respondent_first_name || "",
- middle: caseData.respondent_middle_name || "",
- last: caseData.respondent_last_name || "",
- suffix: caseData.respondent_suffix || ""
- },
- is_new: true,
- });
- }
+ // Build user object
+ const mainUser = {
+ mobile_number: userData.phone,
+ phone_number: userData.phone,
+ address: {
+ address: userData.address,
+ unit: userData.addressLine2,
+ city: userData.city,
+ state: userData.state,
+ zip: userData.zip,
+ country: "US"
+ },
+ email: userData.email,
+ party_type: partyType,
+ date_of_birth: "",
+ is_form_filler: true,
+ name: {
+ first: firstName,
+ middle: middleName,
+ last: lastName,
+ suffix: ""
+ },
+ is_new: true
+ };
+
+ const users = [mainUser];
+
+ // Add second user if needed for name changes
+ if (caseData.new_name_party_type) {
+ users.push({
+ ...mainUser,
+ party_type: caseData.new_name_party_type,
+ name: {
+ first: caseData.new_first_name || firstName,
+ middle: caseData.new_middle_name || middleName,
+ last: caseData.new_last_name || lastName,
+ suffix: caseData.new_suffix || ""
+ }
+ });
+ }
- let other_parties = [];
-
- if (caseData.other_first_name && caseData.other_party_type) {
- other_parties.push({
- party_type: caseData.other_party_type,
- name: {
- first: caseData.other_first_name,
- last: caseData.other_last_name
- },
- address: {
- address: caseData.other_address_line_1,
- unit: caseData.other_address_line_2,
- city: caseData.other_address_city,
- state: caseData.other_address_state,
- zip: caseData.other_address_zip,
- country: "US"
- },
- email: caseData.other_email,
- phone_number: caseData.other_phone_number,
- is_new: true,
- });
- }
+ // Add second user if needed for name changes
+ if (caseData.respondent_name_party_type) {
+ users.push({
+ party_type: caseData.respondent_name_party_type,
+ name: {
+ first: caseData.respondent_first_name || "",
+ middle: caseData.respondent_middle_name || "",
+ last: caseData.respondent_last_name || "",
+ suffix: caseData.respondent_suffix || ""
+ },
+ is_new: true,
+ });
+ }
- const efilingData = {
- efile_case_category: caseData.case_category,
- efile_case_type: caseData.case_type,
- efile_case_subtype: caseData.case_subtype,
- previous_case_id: caseData?.previous_case_id,
- docket_number: caseData?.docket_number,
- users,
- other_parties,
- user_started_case: !caseData?.previous_case_id,
- al_court_bundle: [],
- cross_references: "",
- comments_to_clerk: "",
- tyler_payment_id: paymentAccountID,
- lead_contact: {
- name: { first: firstName, middle: middleName, last: lastName },
- email: userData.email
- },
- return_date: ""
- };
-
- // Add court bundles for documents
- this.addCourtBundles(efilingData, uploadData, caseData, users);
-
- return efilingData;
- },
-
- addCourtBundles(efilingData, uploadData, caseData, users) {
- const courtName = caseData.court_name || caseData.court || "";
- if (courtName.toLowerCase().includes("cook") || courtName.toLowerCase().includes("dupage")) {
- efilingData.cross_references = { 254500: "254500" };
- }
+ let other_parties = [];
+
+ if (caseData.other_first_name && caseData.other_party_type) {
+ other_parties.push({
+ party_type: caseData.other_party_type,
+ name: {
+ first: caseData.other_first_name,
+ last: caseData.other_last_name
+ },
+ address: {
+ address: caseData.other_address_line_1,
+ unit: caseData.other_address_line_2,
+ city: caseData.other_address_city,
+ state: caseData.other_address_state,
+ zip: caseData.other_address_zip,
+ country: "US"
+ },
+ email: caseData.other_email,
+ phone_number: caseData.other_phone_number,
+ is_new: true,
+ });
+ }
- // Add lead document
- if (uploadData?.files?.lead) {
- const leadBundle = this.createDocumentBundle(
- uploadData.files.lead,
- uploadData.lead_filing_type || caseData.filing_type,
- uploadData.lead_document_type || caseData.document_type,
- uploadData.lead_filing_component || caseData.filing_component,
- users,
- uploadData.lead_filing_type_name || caseData.case_type_name,
- uploadData.lead_document_type_name || "",
- uploadData.lead_cc_email
- );
- efilingData.al_court_bundle.push(leadBundle);
- }
+ const efilingData = {
+ efile_case_category: caseData.case_category,
+ efile_case_type: caseData.case_type,
+ efile_case_subtype: caseData.case_subtype,
+ previous_case_id: caseData?.previous_case_id,
+ docket_number: caseData?.docket_number,
+ users,
+ other_parties,
+ user_started_case: !caseData?.previous_case_id,
+ al_court_bundle: [],
+ cross_references: "",
+ comments_to_clerk: "",
+ tyler_payment_id: paymentAccountID,
+ lead_contact: {
+ name: {
+ first: firstName,
+ middle: middleName,
+ last: lastName
+ },
+ email: userData.email
+ },
+ return_date: ""
+ };
+
+ // Add court bundles for documents
+ this.addCourtBundles(efilingData, uploadData, caseData, users);
+
+ return efilingData;
+ },
+
+ addCourtBundles(efilingData, uploadData, caseData, users) {
+ const courtName = caseData.court_name || caseData.court || "";
+ if (courtName.toLowerCase().includes("cook") || courtName.toLowerCase().includes("dupage")) {
+ efilingData.cross_references = {
+ 254500: "254500"
+ };
+ }
- // Add supporting documents
- if (uploadData?.files?.supporting?.length > 0) {
- uploadData.files.supporting.forEach((doc, index) => {
- const config = uploadData.supporting_documents?.[index] || {};
- const bundle = this.createDocumentBundle(
- doc,
- config.filing_type || caseData.filing_type_id,
- config.document_type || caseData.document_type,
- config.filing_component || "supporting",
- users,
- config.filing_type_name || `Supporting Document ${index + 1}`,
- config.document_type_name || "",
- config.cc_email
- );
- efilingData.al_court_bundle.push(bundle);
- });
- }
- },
+ // Add lead document
+ if (uploadData?.files?.lead) {
+ const leadBundle = this.createDocumentBundle(
+ uploadData.files.lead,
+ uploadData.lead_filing_type || caseData.filing_type,
+ uploadData.lead_document_type || caseData.document_type,
+ uploadData.lead_filing_component || caseData.filing_component,
+ users,
+ uploadData.lead_filing_type_name || caseData.case_type_name,
+ uploadData.lead_document_type_name || "",
+ uploadData.lead_cc_email
+ );
+ efilingData.al_court_bundle.push(leadBundle);
+ }
- createDocumentBundle(doc, filingType, documentType, filingComponent, users, description, docDescription, cc_email) {
- if (cc_email) {
- courtesy_copies = [cc_email]
- }
- else {
- courtesy_copies = []
- }
- return {
- proxy_enabled: true,
- filing_type: filingType,
- optional_services: [],
- due_date: null,
- filing_description: description,
- reference_number: "",
- filing_attorney: "",
- filing_comment: "",
- courtesy_copies: courtesy_copies,
- preliminary_copies: [],
- filing_parties: users.length === 1 ? ["users[0]"] : ["users[0]", "users[1]"],
- filing_action: "efile",
- tyler_merge_attachments: false,
- document_type: documentType,
- filing_component: filingComponent,
- filename: doc.name,
- document_description: docDescription,
- data_url: doc.url || doc.s3_url || doc.file_url || doc.download_url
- };
- },
-
- handleSubmissionResult(result) {
- if (result?.success) {
- Messages.showSuccess(gettext("Filing submitted successfully! You will be redirected to the confirmation page."));
- setTimeout(() => {
- const jurisdiction = apiUtils.getCurrentJurisdiction();
- window.location.href = result.redirect_url || `/jurisdiction/${jurisdiction}/filing-confirmation/`;
- }, 2000);
- } else {
- Messages.showError(result?.error || "An error occurred during submission.");
- this.setSubmissionState(false);
+ // Add supporting documents
+ if (uploadData?.files?.supporting?.length > 0) {
+ uploadData.files.supporting.forEach((doc, index) => {
+ const config = uploadData.supporting_documents?.[index] || {};
+ const bundle = this.createDocumentBundle(
+ doc,
+ config.filing_type || caseData.filing_type_id,
+ config.document_type || caseData.document_type,
+ config.filing_component || "supporting",
+ users,
+ config.filing_type_name || `Supporting Document ${index + 1}`,
+ config.document_type_name || "",
+ config.cc_email
+ );
+ efilingData.al_court_bundle.push(bundle);
+ });
+ }
+ },
+
+ createDocumentBundle(doc, filingType, documentType, filingComponent, users, description, docDescription, cc_email) {
+ if (cc_email) {
+ courtesy_copies = [cc_email]
+ } else {
+ courtesy_copies = []
+ }
+ return {
+ proxy_enabled: true,
+ filing_type: filingType,
+ optional_services: [],
+ due_date: null,
+ filing_description: description,
+ reference_number: "",
+ filing_attorney: "",
+ filing_comment: "",
+ courtesy_copies: courtesy_copies,
+ preliminary_copies: [],
+ filing_parties: users.length === 1 ? ["users[0]"] : ["users[0]", "users[1]"],
+ filing_action: "efile",
+ tyler_merge_attachments: false,
+ document_type: documentType,
+ filing_component: filingComponent,
+ filename: doc.name,
+ document_description: docDescription,
+ data_url: doc.url || doc.s3_url || doc.file_url || doc.download_url
+ };
+ },
+
+ handleSubmissionResult(result) {
+ if (result?.success) {
+ Messages.showSuccess(gettext("Filing submitted successfully! You will be redirected to the confirmation page."));
+ setTimeout(() => {
+ const jurisdiction = apiUtils.getCurrentJurisdiction();
+ window.location.href = result.redirect_url || `/jurisdiction/${jurisdiction}/filing-confirmation/`;
+ }, 2000);
+ } else {
+ Messages.showError(result?.error || "An error occurred during submission.");
+ this.setSubmissionState(false);
+ }
}
- }
};
// Main application initialization
const ReviewApp = {
- async init() {
- await this.loadAllData();
- PaymentHandler.handleCallback();
- APIHandlers.fetchPartyType();
- },
-
- async loadAllData() {
- const caseData = DataManager.getCaseData();
- const friendlyNames = DataManager.getFriendlyNames();
-
- // Update case info immediately if available
- if (Object.keys(caseData).length > 0 || Object.keys(friendlyNames).length > 0) {
- UIUpdater.updateCaseInfo(caseData, friendlyNames);
- }
+ async init() {
+ await this.loadAllData();
+ PaymentHandler.handleCallback();
+ APIHandlers.fetchPartyType();
+ },
+
+ async loadAllData() {
+ const caseData = DataManager.getCaseData();
+ const friendlyNames = DataManager.getFriendlyNames();
+
+ // Update case info immediately if available
+ if (Object.keys(caseData).length > 0 || Object.keys(friendlyNames).length > 0) {
+ UIUpdater.updateCaseInfo(caseData, friendlyNames);
+ }
- // Load other data in parallel
- await Promise.all([
- APIHandlers.loadUserInfo(),
- APIHandlers.loadUploadData(),
- APIHandlers.loadPaymentAccounts()
- ]);
- }
+ // Load other data in parallel
+ await Promise.all([
+ APIHandlers.loadUserInfo(),
+ APIHandlers.loadUploadData(),
+ APIHandlers.loadPaymentAccounts()
+ ]);
+ }
};
// Global function exports for HTML onclick handlers
@@ -795,4 +829,4 @@ window.PaymentHandler = PaymentHandler;
window.Navigation = Navigation;
// Initialize app when DOM is ready
-document.addEventListener("DOMContentLoaded", () => ReviewApp.init());
+document.addEventListener("DOMContentLoaded", () => ReviewApp.init());
\ No newline at end of file
diff --git a/efile_app/efile/static/js/upload-handler-first.js b/efile_app/efile/static/js/upload-handler-first.js
index 2c11398..de6d5ff 100644
--- a/efile_app/efile/static/js/upload-handler-first.js
+++ b/efile_app/efile/static/js/upload-handler-first.js
@@ -12,11 +12,11 @@ class UploadHandler {
this.uploadProgress = document.getElementById('uploadProgress');
this.errorAlert = document.getElementById('errorAlert');
this.successAlert = document.getElementById('successAlert');
-
+
this.uploadedFile = null;
-
+
this.initialized = false;
-
+
this.init();
}
@@ -25,13 +25,13 @@ class UploadHandler {
console.warn('UploadHandler already initialized, skipping...');
return;
}
-
+
// First, sync any localStorage data to session
await this.syncFormDataToSession();
this.setupEventListeners();
this.setupDragAndDrop();
-
+
this.initialized = true;
}
@@ -58,7 +58,7 @@ class UploadHandler {
}
async saveUploadDataToSession(uploadData) {
- try {
+ try {
const result = await apiUtils.saveFirstUploadData(uploadData);
if (!result.success) {
throw new Error(result.error || 'Failed to save upload data to session');
@@ -72,10 +72,10 @@ class UploadHandler {
setupEventListeners() {
// Form submission
if (this.form) {
- this.form.addEventListener('submit', (e) => {
- e.preventDefault();
- this.handleFormSubmission();
- });
+ this.form.addEventListener('submit', (e) => {
+ e.preventDefault();
+ this.handleFormSubmission();
+ });
}
// File input changes
@@ -89,7 +89,7 @@ class UploadHandler {
this.leadDocumentArea.addEventListener('click', (e) => {
// Check if click is on file preview or remove button
- if (e.target.closest('.file-preview') ||
+ if (e.target.closest('.file-preview') ||
e.target.closest('.file-remove') ||
e.target.classList.contains('file-remove')) {
return;
@@ -101,11 +101,11 @@ class UploadHandler {
}
lastLeadClick = now;
-
+
// Prevent default and stop propagation to avoid any interference
e.preventDefault();
e.stopPropagation();
-
+
// Use setTimeout to ensure this runs after any other event handlers
setTimeout(() => {
this.leadDocumentInput.click();
@@ -129,7 +129,7 @@ class UploadHandler {
area.addEventListener('drop', (e) => {
e.preventDefault();
area.classList.remove('dragover');
-
+
const files = e.dataTransfer.files;
this.handleFileSelection(files);
});
@@ -196,8 +196,8 @@ class UploadHandler {
// Add file previews
if (file) {
- const preview = this.createFilePreview(file);
- area.appendChild(preview);
+ const preview = this.createFilePreview(file);
+ area.appendChild(preview);
}
// Show/hide document options based on whether files are uploaded
@@ -216,9 +216,9 @@ class UploadHandler {
createFilePreview(file) {
const preview = document.createElement('div');
preview.className = 'file-preview';
-
+
const fileSize = this.formatFileSize(file.size);
-
+
preview.innerHTML = `
@@ -231,7 +231,7 @@ class UploadHandler {
`;
-
+
// Add event listener to the remove button with strong event prevention
const removeButton = preview.querySelector('.file-remove');
removeButton.addEventListener('click', (e) => {
@@ -240,22 +240,22 @@ class UploadHandler {
e.stopImmediatePropagation();
this.uploadedFile = null;
- this.updateFilePreview(this.leadDocumentArea, null);
+ this.updateFilePreview(this.leadDocumentArea, null);
// Clear the native file input
if (this.leadDocumentInput) {
this.leadDocumentInput.value = '';
}
this.updateSubmitButton();
-
+
// Return false to ensure no further event processing
return false;
}, true); // Use capture phase to intercept before other handlers
-
+
// Also add a mousedown event to completely prevent any interaction issues
removeButton.addEventListener('mousedown', (e) => {
e.stopPropagation();
}, true);
-
+
return preview;
}
@@ -282,7 +282,7 @@ class UploadHandler {
});
const result = await response.json();
-
+
if (!result.success) {
throw new Error(result.error || 'Upload failed');
}
@@ -302,7 +302,7 @@ class UploadHandler {
showFileUploadProgress(fileName, type, index) {
const selector = '.file-preview';
const preview = this.leadDocumentArea.querySelector(selector)
-
+
if (preview) {
const statusDiv = preview.querySelector('.upload-status') || document.createElement('div');
statusDiv.className = 'upload-status';
@@ -316,7 +316,7 @@ class UploadHandler {
showFileUploadSuccess(fileName, index) {
const selector = '.file-preview';
const preview = this.leadDocumentArea.querySelector(selector);
-
+
if (preview) {
const statusDiv = preview.querySelector('.upload-status');
if (statusDiv) {
@@ -328,7 +328,7 @@ class UploadHandler {
showFileUploadError(fileName, index, error) {
const selector = '.file-preview';
const preview = this.leadDocumentArea.querySelector(selector)
-
+
if (preview) {
const statusDiv = preview.querySelector('.upload-status');
if (statusDiv) {
@@ -350,7 +350,7 @@ class UploadHandler {
window.location.href = `/jurisdiction/${jurisdiction}/expert_form/`;
return;
}
-
+
this.showWaiting("Processing your form...");
try {
@@ -379,7 +379,7 @@ class UploadHandler {
// Save the complete upload data to session
await this.saveUploadDataToSession(uploadDataWithUrls);
-
+
// Redirect to next page
const jurisdiction = apiUtils.getCurrentJurisdiction();
window.location.href = `/jurisdiction/${jurisdiction}/expert_form/`;
@@ -396,9 +396,12 @@ class UploadHandler {
this.hideAlerts();
document.getElementById('errorMessage').textContent = message;
this.errorAlert.style.display = 'block';
-
+
// Scroll to error
- this.errorAlert.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ this.errorAlert.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center'
+ });
}
showWaiting(message) {
@@ -406,9 +409,12 @@ class UploadHandler {
document.getElementById('successMessage').textContent = message;
document.getElementById('submitButton').disabled = true;
this.successAlert.style.display = 'block';
-
+
// Scroll to success
- this.successAlert.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ this.successAlert.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center'
+ });
}
hideAlerts() {
@@ -418,11 +424,11 @@ class UploadHandler {
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];
}
}
@@ -434,4 +440,4 @@ document.addEventListener('DOMContentLoaded', function() {
} else {
console.warn('UploadHandler already exists, skipping initialization');
}
-});
+});
\ No newline at end of file
diff --git a/efile_app/efile/static/js/upload-handler.js b/efile_app/efile/static/js/upload-handler.js
index c1cb3e2..11c4340 100644
--- a/efile_app/efile/static/js/upload-handler.js
+++ b/efile_app/efile/static/js/upload-handler.js
@@ -29,9 +29,9 @@ class UploadHandler {
this.globalFilingTypes = [];
this.jurisdiction = apiUtils.getCurrentJurisdiction();
-
+
this.initialized = false;
-
+
this.init();
}
@@ -40,7 +40,7 @@ class UploadHandler {
console.warn('UploadHandler already initialized, skipping...');
return;
}
-
+
await this.loadFilingComponents();
// First, sync any localStorage data to session
@@ -49,9 +49,9 @@ class UploadHandler {
this.setupEventListeners();
this.setupDragAndDrop();
this.setupListeners();
-
+
this.setupCascadingDropdowns();
-
+
this.initialized = true;
}
@@ -76,7 +76,7 @@ class UploadHandler {
const leadCertifiedCopies = document.getElementById('leadCertifiedCopies');
if (leadCertifiedCopies) {
- leadCertifiedCopies.addEventListener('change', (e) => {
+ leadCertifiedCopies.addEventListener('change', (e) => {
let inputElement = document.getElementById('leadCertifiedCopyEmail');
this.toggleCCEmail(inputElement, e.target.checked)
})
@@ -123,7 +123,7 @@ class UploadHandler {
}
async saveUploadDataToSession(uploadData) {
- try {
+ try {
const result = await apiUtils.saveUploadData(uploadData);
if (!result.success) {
throw new Error(result.error || 'Failed to save upload data to session');
@@ -186,10 +186,10 @@ class UploadHandler {
setupEventListeners() {
// Form submission
if (this.form) {
- this.form.addEventListener('submit', (e) => {
- e.preventDefault();
- this.handleFormSubmission();
- });
+ this.form.addEventListener('submit', (e) => {
+ e.preventDefault();
+ this.handleFormSubmission();
+ });
}
this.supportingDocumentsInput.addEventListener('change', (e) => {
@@ -203,7 +203,7 @@ class UploadHandler {
this.leadDocumentArea.addEventListener('click', (e) => {
// Check if click is on file preview or remove button
- if (e.target.closest('.file-preview') ||
+ if (e.target.closest('.file-preview') ||
e.target.closest('.file-remove') ||
e.target.classList.contains('file-remove')) {
return;
@@ -215,7 +215,7 @@ class UploadHandler {
}
lastLeadClick = now;
-
+
// Prevent default and stop propagation to avoid any interference
e.preventDefault();
e.stopPropagation();
@@ -223,7 +223,7 @@ class UploadHandler {
this.supportingDocumentsArea.addEventListener('click', (e) => {
// Check if click is on file preview or remove button
- if (e.target.closest('.file-preview') ||
+ if (e.target.closest('.file-preview') ||
e.target.closest('.file-remove') ||
e.target.classList.contains('file-remove')) {
return;
@@ -235,11 +235,11 @@ class UploadHandler {
}
lastSupportingClick = now;
-
+
// Prevent default and stop propagation to avoid any interference
e.preventDefault();
e.stopPropagation();
-
+
// Use setTimeout to ensure this runs after any other event handlers
setTimeout(() => {
this.supportingDocumentsInput.click();
@@ -266,7 +266,7 @@ class UploadHandler {
area.addEventListener('drop', (e) => {
e.preventDefault();
area.classList.remove('dragover');
-
+
const files = e.dataTransfer.files;
this.handleFileSelection(files, type);
});
@@ -287,7 +287,10 @@ class UploadHandler {
if (upload_data.lead_filing_type) {
this.initializeFilingTypeDropdown(document.getElementById("leadFilingType_search"));
- window[`leadFilingTypeDropdown`].selectOption({"text": upload_data.lead_filing_type_name, "value": upload_data.lead_filing_type});
+ window[`leadFilingTypeDropdown`].selectOption({
+ "text": upload_data.lead_filing_type_name,
+ "value": upload_data.lead_filing_type
+ });
await this.populateDocumentTypes(upload_data.lead_filing_type, document.getElementById("leadDocumentType"));
}
@@ -312,7 +315,10 @@ class UploadHandler {
if (d.filing_type) {
this.initializeFilingTypeDropdown(document.getElementById(`supportingFilingType${index}_search`));
- window[`supportingFilingType${index}Dropdown`].selectOption({"text": d.filing_type_name, "value": d.filing_type});
+ window[`supportingFilingType${index}Dropdown`].selectOption({
+ "text": d.filing_type_name,
+ "value": d.filing_type
+ });
await this.populateDocumentTypes(d.filing_type, document.getElementById(`supportingDocumentType${index}`));
}
@@ -344,8 +350,8 @@ class UploadHandler {
// Multiple supporting documents allowed
const startIndex = this.uploadedFiles.length;
- this.uploadedFiles= [...this.uploadedFiles, ...validFiles];
- this.uploadedFileStatuses= [...this.uploadedFileStatuses, validFiles.map(f => FileStatus.UPLOADING)];
+ this.uploadedFiles = [...this.uploadedFiles, ...validFiles];
+ this.uploadedFileStatuses = [...this.uploadedFileStatuses, validFiles.map(f => FileStatus.UPLOADING)];
this.updateFilePreview(this.supportingDocumentsArea, this.uploadedFiles, this.uploadedFileStatuses);
// Update native supporting input FileList to match uploadedFiles.supporting
@@ -420,9 +426,9 @@ class UploadHandler {
createFilePreviewNoRemove(file_name, file_size) {
const preview = document.createElement('div');
preview.className = 'file-preview-lead';
-
+
const fileSize = this.formatFileSize(file_size);
-
+
preview.innerHTML = `
@@ -439,9 +445,9 @@ class UploadHandler {
createFilePreview(file, index) {
const preview = document.createElement('div');
preview.className = 'file-preview';
-
+
const fileSize = this.formatFileSize(file.size);
-
+
preview.innerHTML = `
@@ -454,7 +460,7 @@ class UploadHandler {
`;
-
+
// Add event listener to the remove button with strong event prevention
const removeButton = preview.querySelector('.file-remove');
removeButton.addEventListener('click', (e) => {
@@ -464,40 +470,40 @@ class UploadHandler {
e.stopImmediatePropagation();
this.updateSubmitButton();
-
+
// Return false to ensure no further event processing
return false;
}, true); // Use capture phase to intercept before other handlers
-
+
// Also add a mousedown event to completely prevent any interaction issues
removeButton.addEventListener('mousedown', (e) => {
e.stopPropagation();
}, true);
-
+
return preview;
}
updateSupportingDocumentsOptions(files) {
const optionsContainer = document.getElementById('supportingDocumentsOptions');
if (!optionsContainer) return;
-
+
// Clear existing options
optionsContainer.innerHTML = '';
-
+
// Add options for each supporting document
files.forEach((file, index) => {
const optionsHTML = createSupportingDocumentOptions(index, file.name);
-
+
const div = document.createElement('div');
div.innerHTML = optionsHTML;
optionsContainer.appendChild(div.firstElementChild);
});
-
+
// Initialize search dropdowns for supporting documents
this.initializeSupportingFilingTypeDropdowns(files);
}
- async initializeSupportingFilingTypeDropdowns(files) {
+ async initializeSupportingFilingTypeDropdowns(files) {
// Use global filing types data if available, otherwise wait for it to load
const checkGlobalData = () => {
if (this.globalFilingTypes && this.globalFilingTypes.length > 0) {
@@ -509,13 +515,13 @@ class UploadHandler {
placeholder: 'Search filing types...'
});
searchDropdown.updateOptions(this.globalFilingTypes);
-
+
// Store reference for later use
window[`supportingFilingType${index}Dropdown`] = searchDropdown;
} else {
console.warn(`Dropdown element not found for supportingFilingType${index}_search`);
}
-
+
// Setup cascading dropdown logic
this.setupSupportingDocumentCascading(index);
});
@@ -524,7 +530,7 @@ class UploadHandler {
setTimeout(checkGlobalData, 100);
}
};
-
+
checkGlobalData();
}
@@ -534,7 +540,7 @@ class UploadHandler {
if (filingTypeSelect && documentTypeSelect) {
filingTypeSelect.addEventListener('change', async () => {
- const selectedFilingTypeId = filingTypeSelect.value;
+ const selectedFilingTypeId = filingTypeSelect.value;
if (selectedFilingTypeId) {
await this.populateDocumentTypes(selectedFilingTypeId, documentTypeSelect);
} else {
@@ -545,22 +551,22 @@ class UploadHandler {
}
removeFile(index) {
- this.uploadedFiles.splice(index, 1);
- this.uploadedFileStatuses.splice(index, 1);
- // Regenerate all supporting file previews with correct indices
- this.updateFilePreview(this.supportingDocumentsArea, this.uploadedFiles, this.uploadedFileStatuses);
-
- // Update native supporting input FileList
- try {
- const dt = new DataTransfer();
- this.uploadedFiles.forEach(file => dt.items.add(file));
- if (this.supportingDocumentsInput) {
- this.supportingDocumentsInput.files = dt.files;
- }
- } catch (e) {
- console.warn('Could not update native supporting input.files after removal:', e);
+ this.uploadedFiles.splice(index, 1);
+ this.uploadedFileStatuses.splice(index, 1);
+ // Regenerate all supporting file previews with correct indices
+ this.updateFilePreview(this.supportingDocumentsArea, this.uploadedFiles, this.uploadedFileStatuses);
+
+ // Update native supporting input FileList
+ try {
+ const dt = new DataTransfer();
+ this.uploadedFiles.forEach(file => dt.items.add(file));
+ if (this.supportingDocumentsInput) {
+ this.supportingDocumentsInput.files = dt.files;
}
-
+ } catch (e) {
+ console.warn('Could not update native supporting input.files after removal:', e);
+ }
+
this.updateSubmitButton();
}
@@ -603,7 +609,7 @@ class UploadHandler {
});
const result = await response.json();
-
+
if (!result.success) {
this.uploadedFileStatuses[index] = FileStatus.FAILED;
throw new Error(result.error || 'Upload failed');
@@ -625,7 +631,7 @@ class UploadHandler {
this.uploadedFileStatuses[index] = FileStatus.UPLOADING;
const selector = `.file-preview:nth-child(${index + 3})`;
const preview = this.supportingDocumentsArea.querySelector(selector);
-
+
if (preview) {
const statusDiv = preview.querySelector('.upload-status') || document.createElement('div');
statusDiv.className = 'upload-status';
@@ -640,7 +646,7 @@ class UploadHandler {
this.uploadedFileStatuses[index] = FileStatus.SUCCESS;
const selector = `.file-preview:nth-child(${index + 3})`;
const preview = this.supportingDocumentsArea.querySelector(selector);
-
+
if (preview) {
const statusDiv = preview.querySelector('.upload-status') || document.createElement("div");
statusDiv.className = 'upload-status';
@@ -655,7 +661,7 @@ class UploadHandler {
this.uploadedFileStatuses[index] = FileStatus.FAILED;
const selector = `.file-preview:nth-child(${index + 3})`;
const preview = this.supportingDocumentsArea.querySelector(selector);
-
+
if (preview) {
const statusDiv = preview.querySelector('.upload-status') || document.createElement("div");
statusDiv.className = "upload-status";
@@ -681,7 +687,7 @@ class UploadHandler {
// Collect dropdown values for lead document
const leadFilingTypeSelect = document.getElementById('leadFilingType');
const leadDocumentTypeSelect = document.getElementById('leadDocumentType');
-
+
const leadFilingType = leadFilingTypeSelect ? leadFilingTypeSelect.value : '';
const leadFilingTypeName = leadFilingTypeSelect && leadFilingTypeSelect.selectedOptions[0] ? leadFilingTypeSelect.selectedOptions[0].text : '';
const leadDocumentType = leadDocumentTypeSelect ? leadDocumentTypeSelect.value : '';
@@ -710,7 +716,7 @@ class UploadHandler {
if (!document.getElementById(`supportingCertifiedCopies${index}`).checked) {
supportingCCEmail = null;
}
-
+
const supportingDoc = {
filing_type: filingType,
filing_type_name: filingTypeName,
@@ -720,7 +726,7 @@ class UploadHandler {
filing_component_name: componentName,
cc_email: supportingCCEmail
};
-
+
supportingDocuments.push(supportingDoc);
});
@@ -729,7 +735,10 @@ class UploadHandler {
files: [],
options: {
lead: {
- filing_component: {id: leadFilingComponentValue, name: leadFilingComponentName},
+ filing_component: {
+ id: leadFilingComponentValue,
+ name: leadFilingComponentName
+ },
certified_copies: document.getElementById('leadCertifiedCopies')?.checked || false,
sealed_confidential: document.getElementById('leadSealedConfidential')?.checked || false
},
@@ -775,7 +784,7 @@ class UploadHandler {
// Save the complete upload data to session
await this.saveUploadDataToSession(uploadDataWithUrls);
-
+
// Redirect to review page
window.location.href = `/jurisdiction/${this.jurisdiction}/review/`;
@@ -791,18 +800,24 @@ class UploadHandler {
this.hideAlerts();
document.getElementById('errorMessage').textContent = message;
this.errorAlert.style.display = 'block';
-
+
// Scroll to error
- this.errorAlert.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ this.errorAlert.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center'
+ });
}
showSuccess(message) {
this.hideAlerts();
document.getElementById('successMessage').textContent = message;
this.successAlert.style.display = 'block';
-
+
// Scroll to success
- this.successAlert.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ this.successAlert.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center'
+ });
}
hideAlerts() {
@@ -812,11 +827,11 @@ class UploadHandler {
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];
}
@@ -827,16 +842,18 @@ class UploadHandler {
const field = this.form.querySelector(`[name="${key}"]`);
if (field) {
if (field.type === "checkbox" || field.type === "radio") {
- field.checked = Array.isArray(data[key])
- ? data[key].includes(field.value)
- : data[key] === field.value;
+ field.checked = Array.isArray(data[key]) ?
+ data[key].includes(field.value) :
+ data[key] === field.value;
} else {
field.value = Array.isArray(data[key]) ? data[key][0] : data[key];
}
// Trigger change event for dropdowns to update dependent fields
if (field.tagName === "SELECT") {
- field.dispatchEvent(new Event("change", { bubbles: true }));
+ field.dispatchEvent(new Event("change", {
+ bubbles: true
+ }));
}
}
});
@@ -845,40 +862,46 @@ class UploadHandler {
async loadFilingComponents() {
try {
- // Use our backend API endpoint instead of direct Suffolk API call to avoid CORS
- const response = await fetch(`/api/get-filing-components/?jurisdiction=${this.jurisdiction}`, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- "X-CSRFToken": apiUtils.getCSRFToken(),
- },
- });
-
- if (!response.ok) {
- throw new Error(
- `Failed to load filing components: ${response.status}`
- );
- }
-
- const result = await response.json();
- if (result.success && result.data) {
- // TODO(brycew): Make selecting Lead document and attachment more resillient
- result.data.map((component) => {
- if (component.name === "Lead Document") {
- this.globalFilingComponentLead = {id: component.code, name: component.name };
- }
- if (component.name === "Attachments") {
- this.globalFilingComponentSupport = {id: component.code, name: component.name };
- }
+ // Use our backend API endpoint instead of direct Suffolk API call to avoid CORS
+ const response = await fetch(`/api/get-filing-components/?jurisdiction=${this.jurisdiction}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken": apiUtils.getCSRFToken(),
+ },
});
- } else {
- console.error("API returned error:", result.error);
- }
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to load filing components: ${response.status}`
+ );
+ }
+
+ const result = await response.json();
+ if (result.success && result.data) {
+ // TODO(brycew): Make selecting Lead document and attachment more resillient
+ result.data.map((component) => {
+ if (component.name === "Lead Document") {
+ this.globalFilingComponentLead = {
+ id: component.code,
+ name: component.name
+ };
+ }
+ if (component.name === "Attachments") {
+ this.globalFilingComponentSupport = {
+ id: component.code,
+ name: component.name
+ };
+ }
+ });
+ } else {
+ console.error("API returned error:", result.error);
+ }
} catch (error) {
- console.error("Error loading filing components:", error);
+ console.error("Error loading filing components:", error);
}
}
-
+
async initializeSearchDropdowns() {
// Get case data from Django context (passed from the view)
const caseClassification = JSON.parse(document.getElementById("case-classification").textContent);
@@ -890,97 +913,97 @@ class UploadHandler {
const existingCase = sessionStorage.getItem("existing_case") || "no";
if (!court || !caseType) {
- console.warn(
- "Missing court or case_type for filing type dropdown initialization"
- );
- return;
+ console.warn(
+ "Missing court or case_type for filing type dropdown initialization"
+ );
+ return;
}
-
+
let guesses = (await apiUtils.getUploadData())['guesses'];
// Fetch filing types data only once
if (this.globalFilingTypes.length === 0) {
- try {
- const apiUrl = `/api/dropdowns/filing-types/?jurisdiction=${this.jurisdiction}&court=${encodeURIComponent(
+ try {
+ const apiUrl = `/api/dropdowns/filing-types/?jurisdiction=${this.jurisdiction}&court=${encodeURIComponent(
court
)}&case_category=${encodeURIComponent(categoryType)}&case_type=${encodeURIComponent(
caseType
)}&existing_case=${existingCase}&guessed_filing_type=${guesses["filing type"]}`;
- const response = await fetch(apiUrl, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- "X-CSRFToken": apiUtils.getCSRFToken(),
- },
- });
+ const response = await fetch(apiUrl, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken": apiUtils.getCSRFToken(),
+ },
+ });
- if (!response.ok) {
- throw new Error(
- `Failed to load filing types: ${response.status}`
- );
- }
+ if (!response.ok) {
+ throw new Error(
+ `Failed to load filing types: ${response.status}`
+ );
+ }
- const result = await response.json();
- if (result.success && result.data) {
- this.globalFilingTypes = result.data.map((item, index) => {
- const processedItem = {
- value: item.value || item.code || item.id,
- text: item.text || item.name || item.description,
- };
- return processedItem;
- });
- } else {
- console.error("API returned error:", result.error);
- return;
+ const result = await response.json();
+ if (result.success && result.data) {
+ this.globalFilingTypes = result.data.map((item, index) => {
+ const processedItem = {
+ value: item.value || item.code || item.id,
+ text: item.text || item.name || item.description,
+ };
+ return processedItem;
+ });
+ } else {
+ console.error("API returned error:", result.error);
+ return;
+ }
+ } catch (error) {
+ console.error("Error loading filing types:", error);
+ return;
}
- } catch (error) {
- console.error("Error loading filing types:", error);
- return;
- }
}
// Initialize all existing filing type search dropdowns
this.initializeFilingTypeDropdowns();
}
-
+
async populateDocumentTypes(filingTypeId, documentTypeSelect) {
try {
- // Get court data from Django context to pass required parameters
- const court = JSON.parse(document.getElementById("case-classification").textContent)["court"] || sessionStorage.getItem("selected_court");
+ // Get court data from Django context to pass required parameters
+ const court = JSON.parse(document.getElementById("case-classification").textContent)["court"] || sessionStorage.getItem("selected_court");
- if (!court) {
- console.error("Missing court parameter for document types API");
- documentTypeSelect.innerHTML =
- '
Missing court data ';
- return;
- }
-
- let params = {
- jurisdiction: this.jurisdiction,
- court: court,
- parent: filingTypeId,
- }
- const result = await apiUtils.get('/api/dropdowns/document-types', params, true);
- if (result.success && result.data) {
- documentTypeSelect.innerHTML =
- '
Select an option ';
- result.data.forEach((docType) => {
- const option = document.createElement("option");
- option.value = docType.value || docType.code || docType.id;
- option.textContent =
- docType.text || docType.name || docType.description;
- documentTypeSelect.appendChild(option);
- });
- } else {
- console.error("API returned error:", result.error);
- documentTypeSelect.innerHTML =
- '
Error loading document types ';
- }
+ if (!court) {
+ console.error("Missing court parameter for document types API");
+ documentTypeSelect.innerHTML =
+ '
Missing court data ';
+ return;
+ }
+
+ let params = {
+ jurisdiction: this.jurisdiction,
+ court: court,
+ parent: filingTypeId,
+ }
+ const result = await apiUtils.get('/api/dropdowns/document-types', params, true);
+ if (result.success && result.data) {
+ documentTypeSelect.innerHTML =
+ '
Select an option ';
+ result.data.forEach((docType) => {
+ const option = document.createElement("option");
+ option.value = docType.value || docType.code || docType.id;
+ option.textContent =
+ docType.text || docType.name || docType.description;
+ documentTypeSelect.appendChild(option);
+ });
+ } else {
+ console.error("API returned error:", result.error);
+ documentTypeSelect.innerHTML =
+ '
Error loading document types ';
+ }
} catch (error) {
- console.error("Error loading document types:", error);
- documentTypeSelect.innerHTML =
- '
Error loading document types ';
+ console.error("Error loading document types:", error);
+ documentTypeSelect.innerHTML =
+ '
Error loading document types ';
}
}
@@ -988,7 +1011,7 @@ class UploadHandler {
// Lead document cascading dropdowns
const leadFilingTypeSelect = document.getElementById("leadFilingType");
const leadDocumentTypeSelect =
- document.getElementById("leadDocumentType");
+ document.getElementById("leadDocumentType");
if (leadFilingTypeSelect) {
leadFilingTypeSelect.addEventListener("change", () => {
@@ -1004,11 +1027,11 @@ class UploadHandler {
});
}
}
-
+
initializeFilingTypeDropdowns() {
// Initialize filing type search dropdowns
const filingTypeDropdowns = document.querySelectorAll(
- '[id*="FilingType_search"]'
+ '[id*="FilingType_search"]'
);
filingTypeDropdowns.forEach((dropdown) => this.initializeFilingTypeDropdown(dropdown));
@@ -1028,20 +1051,20 @@ class UploadHandler {
}
}
-
+
function populateDropdownFallback(dropdown) {
- if (!dropdown) return;
+ if (!dropdown) return;
- dropdown.innerHTML = `
+ dropdown.innerHTML = `
${gettext("Select a document type")}
${gettext("Lead Document")}
${gettext("Supporting Document")}
${gettext("Exhibit")}
`;
}
-
+
function createSupportingDocumentOptions(index, fileName) {
- const html = `
+ const html = `
Options for: ${fileName}
@@ -1102,8 +1125,8 @@ function createSupportingDocumentOptions(index, fileName) {
`;
- return html;
- }
+ return html;
+}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
@@ -1112,4 +1135,4 @@ document.addEventListener('DOMContentLoaded', function() {
} else {
console.warn('UploadHandler already exists, skipping initialization');
}
-});
+});
\ No newline at end of file
diff --git a/efile_app/efile/templates/efile/choose_jurisdiction.html b/efile_app/efile/templates/efile/choose_jurisdiction.html
index 4cd1c0f..5b1517e 100644
--- a/efile_app/efile/templates/efile/choose_jurisdiction.html
+++ b/efile_app/efile/templates/efile/choose_jurisdiction.html
@@ -2,55 +2,58 @@
{% load i18n %}
- {% translate "Choose your state" as the_title %}
-
-
-
-
{{ the_title }}
-
-
-
-
-
-
-
-
-
-
- {% for value in jurisdiction_details %}
-
-
-
-
-
-
-
-
-
{{value.jurisdiction.display_name}}
-
+ {% translate "Choose your state" as the_title %}
+
+
+
+
{{ the_title }}
+
+
+
+
+
+
+
- {% endfor %}
-
-
-
-
-
-
+
+
+ {% for value in jurisdiction_details %}
+
+
+
+
+
+
+
+
{{ value.jurisdiction.display_name }}
+
+
+
+ {% endfor %}
+
+
+
+
+
diff --git a/efile_app/efile/templates/efile/components/profile_header.html b/efile_app/efile/templates/efile/components/profile_header.html
index b70988e..736e605 100644
--- a/efile_app/efile/templates/efile/components/profile_header.html
+++ b/efile_app/efile/templates/efile/components/profile_header.html
@@ -2,104 +2,94 @@
{% load i18n %}
-
{{jurisdiction}}
+
{{ jurisdiction }}