1153 lines
63 KiB
HTML
1153 lines
63 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block content %}
|
|
<!-- Google Maps JS -->
|
|
<script src="https://maps.googleapis.com/maps/api/js?key={{ GOOGLE_MAPS_API_KEY }}"></script>
|
|
|
|
<div class="container py-5">
|
|
<!-- Breadcrumb -->
|
|
<nav aria-label="breadcrumb" class="mb-4">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'index' %}" class="text-decoration-none">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'voter_list' %}" class="text-decoration-none">Voters</a></li>
|
|
<li class="breadcrumb-item active" aria-current="page">{% if voter.nickname %}{{ voter.nickname }}{% else %}{{ voter.first_name }}{% endif %} {{ voter.last_name }}</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<!-- Header / Demographics Card -->
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-body p-4 p-md-5">
|
|
<div class="row align-items-center">
|
|
<div class="col-md-auto mb-3 mb-md-0">
|
|
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center shadow-sm" style="width: 80px; height: 80px; font-size: 2rem;">
|
|
{% if voter.nickname %}{{ voter.nickname|first }}{% else %}{{ voter.first_name|first }}{% endif %}{{ voter.last_name|first }}
|
|
</div>
|
|
</div>
|
|
<div class="col-md">
|
|
<div class="d-flex align-items-center">
|
|
<h1 class="h3 mb-1 me-3">{% if voter.nickname %}{{ voter.nickname }}{% else %}{{ voter.first_name }}{% endif %} {{ voter.last_name }}</h1>
|
|
{% if can_edit_voter %}
|
|
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editVoterModal">
|
|
<i class="bi bi-pencil me-1"></i>Edit
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
<p class="text-muted mb-0">
|
|
<i class="bi bi-geo-alt me-1"></i> {{ voter.address|default:"No address on file" }}
|
|
</p>
|
|
<div class="mt-2">
|
|
<span class="badge bg-light text-dark border me-1">Voter ID: {{ voter.voter_id|default:"N/A" }}</span>
|
|
<span class="badge bg-light text-dark border me-1">District: {{ voter.district|default:"-" }}</span>
|
|
<span class="badge bg-light text-dark border me-1">Neighborhood: {{ voter.neighborhood|default:"-" }}</span>
|
|
<span class="badge bg-light text-dark border">Precinct: {{ voter.precinct|default:"-" }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-auto text-md-end mt-3 mt-md-0">
|
|
{% if voter.is_targeted %}
|
|
<div class="badge bg-primary mb-2 px-3 py-2 text-uppercase fw-bold" style="font-size: 0.75rem;"><i class="bi bi-bullseye me-2"></i>Targeted Voter</div>
|
|
{% endif %}
|
|
{% if voter.candidate_support == 'supporting' %}
|
|
<div class="h4 text-success mb-0"><i class="bi bi-check-circle-fill me-2"></i>Supporting</div>
|
|
{% elif voter.candidate_support == 'not_supporting' %}
|
|
<div class="h4 text-danger mb-0"><i class="bi bi-x-circle-fill me-2"></i>Not Supporting</div>
|
|
{% else %}
|
|
<div class="h4 text-secondary mb-0"><i class="bi bi-question-circle-fill me-2"></i>Unknown Support</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Left Column: Quick Stats & Likelihood -->
|
|
<div class="col-lg-4">
|
|
<!-- Map Card -->
|
|
<div class="card border-0 shadow-sm mb-4 overflow-hidden">
|
|
<div class="card-header bg-white py-3">
|
|
<h5 class="card-title mb-0">Location Map</h5>
|
|
</div>
|
|
<div id="voterMap" style="height: 250px; background-color: #f8f9fa;">
|
|
{% if not voter.latitude or not voter.longitude %}
|
|
<div class="d-flex align-items-center justify-content-center h-100 flex-column text-muted p-4 text-center">
|
|
<i class="bi bi-map mb-2" style="font-size: 2rem;"></i>
|
|
<p class="small mb-0">No coordinates available for this address.</p>
|
|
<p class="small mb-0">Edit profile to trigger auto-geocoding.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white py-3">
|
|
<h5 class="card-title mb-0">Contact Information</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<ul class="list-unstyled mb-0">
|
|
<li class="mb-3">
|
|
<label class="small text-muted d-block">Email</label>
|
|
<span class="fw-semibold">{{ voter.email|default:"N/A" }}</span>
|
|
</li>
|
|
<li class="mb-3">
|
|
<label class="small text-muted d-block">Phone</label>
|
|
{% if voter.phone %}
|
|
<div class="d-flex align-items-center">
|
|
<span class="fw-semibold me-2">{{ voter.phone }}</span>
|
|
<a href="tel:{{ voter.phone }}" class="btn btn-sm btn-outline-primary py-0 px-2 me-1" title="Call">
|
|
<i class="bi bi-telephone"></i>
|
|
</a>
|
|
<a href="sms:{{ voter.phone }}" class="btn btn-sm btn-outline-secondary py-0 px-2 me-2" title="Text">
|
|
<i class="bi bi-chat-dots"></i>
|
|
</a>
|
|
<span class="badge bg-light text-dark border">{{ voter.get_phone_type_display }}</span>
|
|
</div>
|
|
{% else %}
|
|
<span class="fw-semibold">N/A</span>
|
|
{% endif %}
|
|
</li>
|
|
{% if voter.secondary_phone %}
|
|
<li class="mb-3">
|
|
<label class="small text-muted d-block">Secondary Phone</label>
|
|
<div class="d-flex align-items-center">
|
|
<span class="fw-semibold me-2">{{ voter.secondary_phone }}</span>
|
|
<a href="tel:{{ voter.secondary_phone }}" class="btn btn-sm btn-outline-primary py-0 px-2 me-1" title="Call">
|
|
<i class="bi bi-telephone"></i>
|
|
</a>
|
|
<a href="sms:{{ voter.secondary_phone }}" class="btn btn-sm btn-outline-secondary py-0 px-2 me-2" title="Text">
|
|
<i class="bi bi-chat-dots"></i>
|
|
</a>
|
|
<span class="badge bg-light text-dark border">{{ voter.get_secondary_phone_type_display }}</span>
|
|
</div>
|
|
</li>
|
|
{% endif %}
|
|
<label class="small text-muted d-block">Birthdate</label>
|
|
<span class="fw-semibold">{{ voter.birthdate|date:"M d, Y"|default:"N/A" }}</span>
|
|
</li>
|
|
<li class="mb-3">
|
|
<label class="small text-muted d-block">Prior State</label>
|
|
<span class="fw-semibold">{{ voter.prior_state|default:"N/A" }}</span>
|
|
</li>
|
|
<li>
|
|
<label class="small text-muted d-block">Registration Date</label>
|
|
<span class="fw-semibold">{{ voter.registration_date|date:"M d, Y"|default:"Unknown" }}</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
|
<h5 class="card-title mb-0">Likelihood to Vote</h5>
|
|
{% if can_edit_voter %}
|
|
<button class="btn btn-sm btn-link text-primary p-0" data-bs-toggle="modal" data-bs-target="#addLikelihoodModal">
|
|
<i class="bi bi-plus-circle"></i>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
<div class="card-body">
|
|
{% for likelihood in likelihoods %}
|
|
<div class="d-flex justify-content-between align-items-center mb-3 last-child-mb-0">
|
|
<div>
|
|
<span class="text-muted">{{ likelihood.election_type.name }}</span>
|
|
{% if likelihood.likelihood == 'very_likely' %}
|
|
<span class="badge bg-success ms-2">Very Likely</span>
|
|
{% elif likelihood.likelihood == 'somewhat_likely' %}
|
|
<span class="badge bg-info ms-2">Somewhat Likely</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary ms-2">Not Likely</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if can_edit_voter %}
|
|
<div class="text-nowrap ms-2">
|
|
<button class="btn btn-sm btn-link text-primary p-0 me-2" data-bs-toggle="modal" data-bs-target="#editLikelihoodModal{{ likelihood.id }}">
|
|
<i class="bi bi-pencil small"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-link text-danger p-0" data-bs-toggle="modal" data-bs-target="#deleteLikelihoodModal{{ likelihood.id }}">
|
|
<i class="bi bi-trash small"></i>
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% empty %}
|
|
<p class="text-muted mb-0 italic">No likelihood data available.</p>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white py-3">
|
|
<h5 class="card-title mb-0">Campaign Assets</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="flex-grow-1">
|
|
<span class="text-muted">Yard Sign Status</span>
|
|
</div>
|
|
<div>
|
|
{% if voter.yard_sign == 'has' %}
|
|
<span class="badge bg-warning text-dark">Has Sign</span>
|
|
{% elif voter.yard_sign == 'wants' %}
|
|
<span class="badge bg-info">Wants Sign</span>
|
|
{% else %}
|
|
<span class="badge bg-light text-dark border">None</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<span class="text-muted">Window Sticker Status</span>
|
|
</div>
|
|
<div>
|
|
{% if voter.window_sticker == 'has' %}
|
|
<span class="badge bg-warning text-dark">Has Sticker</span>
|
|
{% elif voter.window_sticker == 'wants' %}
|
|
<span class="badge bg-info">Wants Sticker</span>
|
|
{% else %}
|
|
<span class="badge bg-light text-dark border">None</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header bg-white py-3">
|
|
<h5 class="card-title mb-0">Notes</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="mb-0 text-muted" style="white-space: pre-wrap;">{{ voter.notes|default:"No notes available." }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Column: Detailed Records -->
|
|
<div class="col-lg-8">
|
|
<!-- Nav Tabs -->
|
|
<ul class="nav nav-tabs border-0 mb-4" id="voterTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active border-0 py-3 px-4" id="interactions-tab" data-bs-toggle="tab" data-bs-target="#interactions" type="button" role="tab">Interactions</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link border-0 py-3 px-4" id="events-tab" data-bs-toggle="tab" data-bs-target="#events" type="button" role="tab">Events</button>
|
|
</li>
|
|
{% if can_view_donations %}
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link border-0 py-3 px-4" id="donations-tab" data-bs-toggle="tab" data-bs-target="#donations" type="button" role="tab">Donations</button>
|
|
</li>
|
|
{% endif %}
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link border-0 py-3 px-4" id="voting-tab" data-bs-toggle="tab" data-bs-target="#voting" type="button" role="tab">Voting History</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content" id="voterTabsContent">
|
|
<!-- Interactions Tab -->
|
|
<div class="tab-pane fade show active" id="interactions" role="tabpanel">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0">Interaction History</h6>
|
|
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addInteractionModal">
|
|
<i class="bi bi-plus-lg me-1"></i>New Interaction
|
|
</button>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table align-middle mb-0">
|
|
<thead class="bg-light">
|
|
<tr>
|
|
<th class="ps-4">Date</th>
|
|
<th>Type</th>
|
|
<th>Volunteer</th>
|
|
<th>Description</th>
|
|
<th>Notes</th>
|
|
{% if can_edit_voter %}
|
|
<th class="pe-4 text-end">Actions</th>
|
|
{% endif %}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for interaction in interactions %}
|
|
<tr>
|
|
<td class="ps-4 text-nowrap">{{ interaction.date|date:"M d, Y H:i" }}</td>
|
|
<td><span class="badge bg-light text-dark border">{{ interaction.type.name }}</span></td>
|
|
<td>{% if interaction.volunteer %}{{ interaction.volunteer }}{% else %}<span class="text-muted small">-</span>{% endif %}</td>
|
|
<td>{{ interaction.description }}</td>
|
|
<td class="small text-muted">{{ interaction.notes|truncatechars:30 }}</td>
|
|
{% if can_edit_voter %}
|
|
<td class="pe-4 text-end">
|
|
<button class="btn btn-sm btn-link text-primary p-0 me-2" data-bs-toggle="modal" data-bs-target="#editInteractionModal{{ interaction.id }}">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-link text-danger p-0" data-bs-toggle="modal" data-bs-target="#deleteInteractionModal{{ interaction.id }}">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="6" class="text-center py-4 text-muted">No interactions recorded.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Events Tab -->
|
|
<div class="tab-pane fade" id="events" role="tabpanel">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0">Event Participation</h6>
|
|
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addEventParticipationModal">
|
|
<i class="bi bi-plus-lg me-1"></i>Add Participation
|
|
</button>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table align-middle mb-0">
|
|
<thead class="bg-light">
|
|
<tr>
|
|
<th class="ps-4">Date</th>
|
|
<th>Event Name</th>
|
|
<th>Event Type</th>
|
|
<th>Status</th>
|
|
<th>Description</th>
|
|
{% if can_edit_voter %}
|
|
<th class="pe-4 text-end">Actions</th>
|
|
{% endif %}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for participation in event_participations %}
|
|
<tr>
|
|
<td class="ps-4 text-nowrap">{{ participation.event.date|date:"M d, Y" }}</td>
|
|
<td><strong>{{ participation.event.name|default:"(No Name)" }}</strong></td>
|
|
<td><span class="badge bg-light text-dark border">{{ participation.event.event_type.name }}</span></td>
|
|
<td>
|
|
{% if participation.participation_status.name|lower == 'attended' %}
|
|
<span class="badge bg-success-subtle text-success border border-success-subtle">Attended</span>
|
|
{% elif participation.participation_status.name|lower == "invited but didn't attend" or participation.participation_status.name|lower == "invited but didn't attend" %}
|
|
<span class="badge bg-danger-subtle text-danger border border-danger-subtle">Did Not Attend</span>
|
|
{% else %}
|
|
<span class="badge bg-info-subtle text-info border border-info-subtle">{{ participation.participation_status.name }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="small text-muted">{{ participation.event.description|truncatechars:60 }}</td>
|
|
{% if can_edit_voter %}
|
|
<td class="pe-4 text-end">
|
|
<button class="btn btn-sm btn-link text-primary p-0 me-2" data-bs-toggle="modal" data-bs-target="#editEventParticipationModal{{ participation.id }}">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-link text-danger p-0" data-bs-toggle="modal" data-bs-target="#deleteEventParticipationModal{{ participation.id }}">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="6" class="text-center py-4 text-muted">No event participations found.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Donations Tab -->
|
|
{% if can_view_donations %}
|
|
<div class="tab-pane fade" id="donations" role="tabpanel">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0">Donation History</h6>
|
|
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addDonationModal">
|
|
<i class="bi bi-plus-lg me-1"></i>Add Donation
|
|
</button>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table align-middle mb-0">
|
|
<thead class="bg-light">
|
|
<tr>
|
|
<th class="ps-4">Date</th>
|
|
<th>Method</th>
|
|
<th class="text-end">Amount</th>
|
|
<th class="pe-4 text-end">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for donation in donations %}
|
|
<tr>
|
|
<td class="ps-4 text-nowrap">{{ donation.date|date:"M d, Y" }}</td>
|
|
<td>{{ donation.method.name }}</td>
|
|
<td class="text-end fw-semibold text-success">${{ donation.amount }}</td>
|
|
<td class="pe-4 text-end">
|
|
<button class="btn btn-sm btn-link text-primary p-0 me-2" data-bs-toggle="modal" data-bs-target="#editDonationModal{{ donation.id }}">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-link text-danger p-0" data-bs-toggle="modal" data-bs-target="#deleteDonationModal{{ donation.id }}">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="4" class="text-center py-4 text-muted">No donations recorded.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Voting History Tab -->
|
|
<div class="tab-pane fade" id="voting" role="tabpanel">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="table-responsive">
|
|
<table class="table align-middle mb-0">
|
|
<thead class="bg-light">
|
|
<tr>
|
|
<th class="ps-4">Election Date</th>
|
|
<th>Description</th>
|
|
<th class="pe-4">Party</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for record in voting_records %}
|
|
<tr>
|
|
<td class="ps-4 text-nowrap">{{ record.election_date|date:"M d, Y" }}</td>
|
|
<td>{{ record.election_description }}</td>
|
|
<td class="pe-4">{{ record.primary_party|default:"-" }}</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="3" class="text-center py-4 text-muted">No voting records found.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if can_edit_voter %}
|
|
<!-- Edit Voter Modal -->
|
|
<div class="modal fade" id="editVoterModal" tabindex="-1" aria-labelledby="editVoterModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title" id="editVoterModalLabel">Edit Voter Profile</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form id="editVoterForm" action="{% url 'voter_edit' voter.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<div class="modal-body p-4">
|
|
<div class="row">
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.first_name.label }}</label>
|
|
{{ voter_form.first_name }}
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.last_name.label }}</label>
|
|
{{ voter_form.last_name }}
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.nickname.label }}</label>
|
|
{{ voter_form.nickname }}
|
|
</div>
|
|
<div class="col-md-12 mb-3">
|
|
<label class="form-label fw-medium">Address Street</label>
|
|
{{ voter_form.address_street }}
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label fw-medium">City</label>
|
|
{{ voter_form.city }}
|
|
</div>
|
|
<div class="col-md-2 mb-3">
|
|
<label class="form-label fw-medium">State</label>
|
|
{{ voter_form.state }}
|
|
</div>
|
|
<div class="col-md-2 mb-3">
|
|
<label class="form-label fw-medium">Prior State</label>
|
|
{{ voter_form.prior_state }}
|
|
</div>
|
|
<div class="col-md-2 mb-3">
|
|
<label class="form-label fw-medium">Zip Code</label>
|
|
{{ voter_form.zip_code }}
|
|
</div>
|
|
<div class="col-12 mb-4">
|
|
<button type="button" id="manualGeocodeBtn" class="btn btn-outline-primary w-100 fw-bold py-2 shadow-sm">
|
|
<i class="bi bi-geo-alt-fill me-2"></i>Manual Geocode Address
|
|
</button>
|
|
<div id="geocodeStatus" class="small mt-2 text-center" style="display: none;"></div>
|
|
</div>
|
|
<div class="col-md-12 mb-3">
|
|
<label class="form-label fw-medium">County</label>
|
|
{{ voter_form.county }}
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label fw-medium">Latitude</label>
|
|
{{ voter_form.latitude }}
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label fw-medium">Longitude</label>
|
|
{{ voter_form.longitude }}
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.phone.label }}</label>
|
|
{{ voter_form.phone }}
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.phone_type.label }}</label>
|
|
{{ voter_form.phone_type }}
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.secondary_phone.label }}</label>
|
|
{{ voter_form.secondary_phone }}
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.secondary_phone_type.label }}</label>
|
|
{{ voter_form.secondary_phone_type }}
|
|
</div>
|
|
<div class="col-md-12 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.email.label }}</label>
|
|
{{ voter_form.email }}
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.voter_id.label }}</label>
|
|
{{ voter_form.voter_id }}
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.neighborhood.label }}</label>
|
|
{{ voter_form.neighborhood }}
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.district.label }}</label>
|
|
{{ voter_form.district }}
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.precinct.label }}</label>
|
|
{{ voter_form.precinct }}
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.birthdate.label }}</label>
|
|
{{ voter_form.birthdate }}
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.registration_date.label }}</label>
|
|
{{ voter_form.registration_date }}
|
|
</div>
|
|
<div class="col-md-12 mb-3 d-flex align-items-center">
|
|
<div class="form-check">
|
|
{{ voter_form.is_targeted }}
|
|
<label class="form-check-label fw-medium" for="{{ voter_form.is_targeted.id_for_label }}">
|
|
Targeted Voter
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.candidate_support.label }}</label>
|
|
{{ voter_form.candidate_support }}
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.yard_sign.label }}</label>
|
|
{{ voter_form.yard_sign }}
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-medium">{{ voter_form.window_sticker.label }}</label>
|
|
{{ voter_form.window_sticker }}
|
|
</div>
|
|
<div class="col-md-12 mb-3">
|
|
<label class="form-label fw-medium">Notes</label>
|
|
{{ voter_form.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0 justify-content-between">
|
|
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteVoterModal">
|
|
<i class="bi bi-trash me-1"></i>Delete Profile
|
|
</button>
|
|
<div>
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary px-4">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Delete Voter Confirmation Modal -->
|
|
<div class="modal fade" id="deleteVoterModal" tabindex="-1" aria-labelledby="deleteVoterModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-danger text-white">
|
|
<h5 class="modal-title" id="deleteVoterModalLabel">Delete Voter Profile</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4 text-center">
|
|
<i class="bi bi-exclamation-triangle text-danger mb-3" style="font-size: 3rem;"></i>
|
|
<h4 class="mb-3">Are you sure?</h4>
|
|
<p class="text-muted">This action will permanently delete <strong>{% if voter.nickname %}{{ voter.nickname }}{% else %}{{ voter.first_name }}{% endif %} {{ voter.last_name }}</strong> and all associated data. This cannot be undone.</p>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0 justify-content-center">
|
|
<button type="button" class="btn btn-light px-4" data-bs-dismiss="modal">No, Keep Profile</button>
|
|
<form action="{% url 'voter_delete' voter.id %}" method="POST" class="ms-2">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-danger px-4">Yes, Delete Permanently</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Add Interaction Modal -->
|
|
<div class="modal fade" id="addInteractionModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">New Interaction</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form action="{% url 'add_interaction' voter.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<div class="modal-body p-4">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">{{ interaction_form.type.label }}</label>
|
|
{{ interaction_form.type }}
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">{{ interaction_form.volunteer.label }}</label>
|
|
{{ interaction_form.volunteer }}
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">{{ interaction_form.date.label }}</label>
|
|
{{ interaction_form.date }}
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">{{ interaction_form.description.label }}</label>
|
|
{{ interaction_form.description }}
|
|
</div>
|
|
<div class="mb-0">
|
|
<label class="form-label fw-medium">{{ interaction_form.notes.label }}</label>
|
|
{{ interaction_form.notes }}
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary px-4">Log Interaction</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if can_edit_voter %}
|
|
<!-- Edit Interaction Modals -->
|
|
{% for interaction in interactions %}
|
|
<div class="modal fade" id="editInteractionModal{{ interaction.id }}" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Edit Interaction</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form action="{% url 'edit_interaction' interaction.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<div class="modal-body p-4">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">Type</label>
|
|
<select name="type" class="form-select">
|
|
{% for type in interaction_form.fields.type.queryset %}
|
|
<option value="{{ type.id }}" {% if type.id == interaction.type.id %}selected{% endif %}>{{ type.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">Volunteer</label>
|
|
<select name="volunteer" class="form-select">
|
|
<option value="">---------</option>
|
|
{% for vol in interaction_form.fields.volunteer.queryset %}
|
|
<option value="{{ vol.id }}" {% if vol.id == interaction.volunteer.id %}selected{% endif %}>{{ vol }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">Date</label>
|
|
<input type="datetime-local" name="date" class="form-control" value="{{ interaction.date|date:'Y-m-d\TH:i' }}">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">Description</label>
|
|
<input type="text" name="description" class="form-control" value="{{ interaction.description }}">
|
|
</div>
|
|
<div class="mb-0">
|
|
<label class="form-label fw-medium">Notes</label>
|
|
<textarea name="notes" class="form-control" rows="2">{{ interaction.notes }}</textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0 justify-content-between">
|
|
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteVoterModal">
|
|
<i class="bi bi-trash me-1"></i>Delete Profile
|
|
</button>
|
|
<div>
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary px-4">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Interaction Modal -->
|
|
<div class="modal fade" id="deleteInteractionModal{{ interaction.id }}" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Delete Interaction?</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4 text-center">
|
|
<p class="text-muted mb-0">Are you sure you want to delete this interaction?</p>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0 justify-content-center">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">No, Keep</button>
|
|
<form action="{% url 'delete_interaction' interaction.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-danger">Yes, Delete</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
{% if can_view_donations %}
|
|
<!-- Add Donation Modal -->
|
|
<div class="modal fade" id="addDonationModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Add Donation</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form action="{% url 'add_donation' voter.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<div class="modal-body p-4">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">{{ donation_form.amount.label }}</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text">$</span>
|
|
{{ donation_form.amount }}
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">{{ donation_form.date.label }}</label>
|
|
{{ donation_form.date }}
|
|
</div>
|
|
<div class="mb-0">
|
|
<label class="form-label fw-medium">{{ donation_form.method.label }}</label>
|
|
{{ donation_form.method }}
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary px-4">Record Donation</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Donation Modals -->
|
|
{% for donation in donations %}
|
|
<div class="modal fade" id="editDonationModal{{ donation.id }}" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Edit Donation</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form action="{% url 'edit_donation' donation.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<div class="modal-body p-4">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">Amount</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text">$</span>
|
|
<input type="number" name="amount" class="form-control" step="0.01" value="{{ donation.amount }}">
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">Date</label>
|
|
<input type="date" name="date" class="form-control" value="{{ donation.date|date:'Y-m-d' }}">
|
|
</div>
|
|
<div class="mb-0">
|
|
<label class="form-label fw-medium">Method</label>
|
|
<select name="method" class="form-select">
|
|
{% for method in donation_form.fields.method.queryset %}
|
|
<option value="{{ method.id }}" {% if method.id == donation.method.id %}selected{% endif %}>{{ method.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0 justify-content-between">
|
|
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteVoterModal">
|
|
<i class="bi bi-trash me-1"></i>Delete Profile
|
|
</button>
|
|
<div>
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary px-4">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Donation Modal -->
|
|
<div class="modal fade" id="deleteDonationModal{{ donation.id }}" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Delete Donation?</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4 text-center">
|
|
<p class="text-muted mb-0">Are you sure you want to delete this donation record?</p>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0 justify-content-center">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">No, Keep</button>
|
|
<form action="{% url 'delete_donation' donation.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-danger">Yes, Delete</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
<!-- Add Likelihood Modal -->
|
|
<div class="modal fade" id="addLikelihoodModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Update Voter Likelihood</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form action="{% url 'add_likelihood' voter.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<div class="modal-body p-4">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">{{ likelihood_form.election_type.label }}</label>
|
|
{{ likelihood_form.election_type }}
|
|
</div>
|
|
<div class="mb-0">
|
|
<label class="form-label fw-medium">{{ likelihood_form.likelihood.label }}</label>
|
|
{{ likelihood_form.likelihood }}
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary px-4">Update</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if can_edit_voter %}
|
|
<!-- Edit Likelihood Modals -->
|
|
{% for likelihood in likelihoods %}
|
|
<div class="modal fade" id="editLikelihoodModal{{ likelihood.id }}" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Edit Likelihood</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form action="{% url 'edit_likelihood' likelihood.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<div class="modal-body p-4">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">Election Type</label>
|
|
<select name="election_type" class="form-select">
|
|
{% for et in likelihood_form.fields.election_type.queryset %}
|
|
<option value="{{ et.id }}" {% if et.id == likelihood.election_type.id %}selected{% endif %}>{{ et.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-0">
|
|
<label class="form-label fw-medium">Likelihood</label>
|
|
<select name="likelihood" class="form-select">
|
|
<option value="not_likely" {% if likelihood.likelihood == 'not_likely' %}selected{% endif %}>Not Likely</option>
|
|
<option value="somewhat_likely" {% if likelihood.likelihood == 'somewhat_likely' %}selected{% endif %}>Somewhat Likely</option>
|
|
<option value="very_likely" {% if likelihood.likelihood == 'very_likely' %}selected{% endif %}>Very Likely</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0 justify-content-between">
|
|
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteVoterModal">
|
|
<i class="bi bi-trash me-1"></i>Delete Profile
|
|
</button>
|
|
<div>
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary px-4">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Likelihood Modal -->
|
|
<div class="modal fade" id="deleteLikelihoodModal{{ likelihood.id }}" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Delete Likelihood?</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4 text-center">
|
|
<p class="text-muted mb-0">Delete likelihood for <strong>{{ likelihood.election_type.name }}</strong>?</p>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0 justify-content-center">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">No</button>
|
|
<form action="{% url 'delete_likelihood' likelihood.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-danger">Yes, Delete</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
<!-- Add Event Participation Modal -->
|
|
<div class="modal fade" id="addEventParticipationModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Add Event Participation</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form action="{% url 'add_event_participation' voter.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<div class="modal-body p-4">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">Select Event</label>
|
|
{{ event_participation_form.event }}
|
|
</div>
|
|
<div class="mb-0">
|
|
<label class="form-label fw-medium">Participation Status</label>
|
|
{{ event_participation_form.participation_status }}
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary px-4">Add Participation</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if can_edit_voter %}
|
|
<!-- Edit Event Participation Modals -->
|
|
{% for participation in event_participations %}
|
|
<div class="modal fade" id="editEventParticipationModal{{ participation.id }}" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Edit Participation</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form action="{% url 'edit_event_participation' participation.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<div class="modal-body p-4">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-medium">Event</label>
|
|
<select name="event" class="form-select">
|
|
{% for ev in event_participation_form.fields.event.queryset %}
|
|
<option value="{{ ev.id }}" {% if ev.id == participation.event.id %}selected{% endif %}>{{ ev.event_type.name }} on {{ ev.date }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-0">
|
|
<label class="form-label fw-medium">Participation Status</label>
|
|
<select name="participation_status" class="form-select">
|
|
{% for status in event_participation_form.fields.participation_status.queryset %}
|
|
<option value="{{ status.id }}" {% if status.id == participation.participation_status.id %}selected{% endif %}>{{ status.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0 justify-content-between">
|
|
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteVoterModal">
|
|
<i class="bi bi-trash me-1"></i>Delete Profile
|
|
</button>
|
|
<div>
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary px-4">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Event Participation Modal -->
|
|
<div class="modal fade" id="deleteEventParticipationModal{{ participation.id }}" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-sm">
|
|
<div class="modal-content border-0">
|
|
<div class="modal-header border-0 bg-light">
|
|
<h5 class="modal-title">Remove Participation?</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body p-4 text-center">
|
|
<p class="text-muted mb-0">Are you sure you want to remove this event participation?</p>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0 justify-content-center">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">No</button>
|
|
<form action="{% url 'delete_event_participation' participation.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-danger">Yes, Remove</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
<style>
|
|
.nav-tabs .nav-link {
|
|
color: #6c757d;
|
|
font-weight: 500;
|
|
border-radius: 0;
|
|
}
|
|
.nav-tabs .nav-link.active {
|
|
color: #059669;
|
|
background: transparent;
|
|
border-bottom: 2px solid #059669 !important;
|
|
}
|
|
.last-child-mb-0:last-child {
|
|
margin-bottom: 0 !important;
|
|
}
|
|
.modal-content {
|
|
border-radius: 1rem;
|
|
overflow: hidden;
|
|
}
|
|
.form-control, .form-select {
|
|
border-color: #e5e7eb;
|
|
padding: 0.6rem 0.8rem;
|
|
}
|
|
.form-control:focus, .form-select:focus {
|
|
border-color: #059669;
|
|
box-shadow: 0 0 0 0.25rem rgba(5, 150, 105, 0.1);
|
|
}
|
|
.bg-success-subtle { background-color: #d1fae5; }
|
|
.bg-danger-subtle { background-color: #fee2e2; }
|
|
.bg-info-subtle { background-color: #e0f2fe; }
|
|
</style>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const activeTab = urlParams.get('active_tab');
|
|
if (activeTab) {
|
|
const tabButton = document.getElementById(activeTab + '-tab');
|
|
if (tabButton) {
|
|
const tab = new bootstrap.Tab(tabButton);
|
|
tab.show();
|
|
}
|
|
}
|
|
|
|
{% if voter.latitude and voter.longitude %}
|
|
// Initialize Map
|
|
try {
|
|
const position = { lat: {{ voter.latitude }}, lng: {{ voter.longitude }} };
|
|
const map = new google.maps.Map(document.getElementById("voterMap"), {
|
|
zoom: 15,
|
|
center: position,
|
|
});
|
|
const marker = new google.maps.Marker({
|
|
position: position,
|
|
map: map,
|
|
title: "{% if voter.nickname %}{{ voter.nickname }}{% else %}{{ voter.first_name }}{% endif %} {{ voter.last_name }}",
|
|
});
|
|
const infowindow = new google.maps.InfoWindow({
|
|
content: "<strong>{% if voter.nickname %}{{ voter.nickname }}{% else %}{{ voter.first_name }}{% endif %} {{ voter.last_name }}</strong><br>{{ voter.address_street }}",
|
|
});
|
|
marker.addListener("click", () => {
|
|
infowindow.open(map, marker);
|
|
});
|
|
infowindow.open(map, marker);
|
|
} catch (e) {
|
|
console.error("Google Maps initialization failed:", e);
|
|
}
|
|
{% endif %}
|
|
|
|
// Manual Geocode Logic
|
|
const geocodeBtn = document.getElementById('manualGeocodeBtn');
|
|
const statusDiv = document.getElementById('geocodeStatus');
|
|
|
|
if (geocodeBtn) {
|
|
geocodeBtn.addEventListener('click', function() {
|
|
const street = document.querySelector('[name="address_street"]').value;
|
|
const city = document.querySelector('[name="city"]').value;
|
|
const state = document.querySelector('[name="state"]').value;
|
|
const zip = document.querySelector('[name="zip_code"]').value;
|
|
|
|
if (!street && !city) {
|
|
statusDiv.innerHTML = '<span class="text-danger">Please enter at least a street or city.</span>';
|
|
statusDiv.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
geocodeBtn.disabled = true;
|
|
geocodeBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Geocoding...';
|
|
statusDiv.style.display = 'none';
|
|
|
|
const formData = new FormData();
|
|
formData.append('address_street', street);
|
|
formData.append('city', city);
|
|
formData.append('state', state);
|
|
formData.append('zip_code', zip);
|
|
formData.append('csrfmiddlewaretoken', '{{ csrf_token }}');
|
|
|
|
fetch('{% url "voter_geocode" voter.id %}', {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.querySelector('[name="latitude"]').value = data.latitude;
|
|
document.querySelector('[name="longitude"]').value = String(data.longitude).substring(0, 12);
|
|
statusDiv.innerHTML = '<span class="text-success fw-bold"><i class="bi bi-check-circle me-1"></i>Coordinates updated!</span>';
|
|
} else {
|
|
statusDiv.innerHTML = '<span class="text-danger">' + (data.error || 'Geocoding failed.') + '</span>';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
statusDiv.innerHTML = '<span class="text-danger">An error occurred during geocoding.</span>';
|
|
})
|
|
.finally(() => {
|
|
geocodeBtn.disabled = false;
|
|
geocodeBtn.innerHTML = '<i class="bi bi-geo-alt-fill me-2"></i>Manual Geocode Address';
|
|
statusDiv.style.display = 'block';
|
|
});
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |