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 = ``; - 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 = ``; + 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 = ``; + 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 = ``; - 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 = ``; - 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 = ``; + 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 = ``; + 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 = ``; - 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 = ``; - } - - 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 = ``; + } + + 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 = `" - 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 = `" + break; + + case "party_type_dropdown": + // Create a dropdown for party types that will be populated via API + 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 `
${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 = ''; - - // 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 = - ''; - } - } catch (error) { - console.error(`Error loading party types for ${fieldId}:`, error); - - // Add error option - dropdown.innerHTML = - ''; - } 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 = ''; + + // 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 = + ''; + } + } catch (error) { + console.error(`Error loading party types for ${fieldId}:`, error); + + // Add error option + dropdown.innerHTML = + ''; + } 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}
`; - }); - - 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 += `
@@ -386,23 +410,23 @@ const UIUpdater = {
`; - }); + }); - html += `
+ html += `
`; - 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 = - ''; - 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 = - ''; - 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 = - ''; - } + if (!court) { + console.error("Missing court parameter for document types API"); + documentTypeSelect.innerHTML = + ''; + 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 = + ''; + 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 = + ''; + } } catch (error) { - console.error("Error loading document types:", error); - documentTypeSelect.innerHTML = - ''; + console.error("Error loading document types:", error); + documentTypeSelect.innerHTML = + ''; } } @@ -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 = ` `; } - + 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 }} - - - - - - -
-
-
-
- -

{{ the_title }}

-
-
-
-
- -
-
- {% for value in jurisdiction_details %} -
- - -
-
- -
-
-

{{value.jurisdiction.display_name}}

-
+ {% translate "Choose your state" as the_title %} + + + + {{ the_title }} + + + + + + +
+
+
+
+ +

{{ the_title }}

+
+
-
- {% endfor %} -
-
- - - - +
+
+ {% for value in jurisdiction_details %} +
+ +
+
+ {% static value.jurisdiction.display_name %} +
+
+

{{ 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 %} - +
-
-
-
- -

{{ config.jurisdiction.name }}

-
-
-
- +
+
+
+ +

{{ config.jurisdiction.name }}

+
+
+
+ +
+
-
-
-