Added comments/reports under SAR jobs
GPX functionality still WIP
This commit is contained in:
parent
e124209da0
commit
cd6c2c216d
@ -11,6 +11,8 @@ class User(UserMixin, db.Model):
|
|||||||
password = db.Column(db.String(50))
|
password = db.Column(db.String(50))
|
||||||
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
|
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
|
||||||
role = db.relationship('Role', backref=db.backref('users', lazy='dynamic'))
|
role = db.relationship('Role', backref=db.backref('users', lazy='dynamic'))
|
||||||
|
def __repr__(self):
|
||||||
|
return self.username
|
||||||
|
|
||||||
class Role(db.Model):
|
class Role(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
@ -32,8 +34,7 @@ class SARCall(db.Model):
|
|||||||
description = db.Column(db.Text, nullable=True)
|
description = db.Column(db.Text, nullable=True)
|
||||||
description_hidden = db.Column(db.Text, nullable=True)
|
description_hidden = db.Column(db.Text, nullable=True)
|
||||||
search_manager_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
search_manager_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
gpx_data = db.Column(db.Text, nullable=True) # This will store GPX data as a text
|
search_manager = db.relationship('User', backref=db.backref('sar_calls', lazy=True))
|
||||||
|
|
||||||
|
|
||||||
class SARCategory(db.Model):
|
class SARCategory(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False)
|
id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False)
|
||||||
@ -63,7 +64,7 @@ class GPSTrack(db.Model):
|
|||||||
class Comment(db.Model):
|
class Comment(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
text = db.Column(db.Text, nullable=True)
|
text = db.Column(db.Text, nullable=True)
|
||||||
gpx_data = db.Column(db.Text, nullable=True)
|
gpx_data = db.Column(db.Text (length=4294967295) , nullable=True)
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
user = db.relationship('User', backref=db.backref('comments', lazy=True))
|
user = db.relationship('User', backref=db.backref('comments', lazy=True))
|
||||||
sar_call_id = db.Column(db.Integer, db.ForeignKey('sar_call.id'), nullable=False)
|
sar_call_id = db.Column(db.Integer, db.ForeignKey('sar_call.id'), nullable=False)
|
||||||
|
39
sar_calls.py
39
sar_calls.py
@ -1,8 +1,8 @@
|
|||||||
from app import app, db
|
from app import app, db
|
||||||
from flask import request, redirect, flash, render_template, url_for
|
from flask import request, redirect, flash, render_template, url_for, jsonify
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
from models import SARCall, GPSTrack, SARCategory, SARStatus, User
|
from models import SARCall, Comment, GPSTrack, SARCategory, SARStatus, User
|
||||||
|
|
||||||
|
|
||||||
@app.route('/create_sar', methods=['GET', 'POST'])
|
@app.route('/create_sar', methods=['GET', 'POST'])
|
||||||
@ -114,30 +114,29 @@ def add_comment(sar_call_id):
|
|||||||
comment = Comment(text=text, gpx_data=gpx_data, user_id=current_user.id, sar_call_id=sar_call_id)
|
comment = Comment(text=text, gpx_data=gpx_data, user_id=current_user.id, sar_call_id=sar_call_id)
|
||||||
db.session.add(comment)
|
db.session.add(comment)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('view_sar', sar_call_id=sar_call_id))
|
return redirect(url_for('sar_details', id=sar_call_id))
|
||||||
|
|
||||||
@app.route('/edit_comment/<int:comment_id>', methods=['GET', 'POST'])
|
|
||||||
|
@app.route('/edit_comment/<int:comment_id>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def edit_comment(comment_id):
|
def edit_comment(comment_id):
|
||||||
comment = Comment.query.get_or_404(comment_id)
|
comment = Comment.query.get_or_404(comment_id)
|
||||||
if current_user.id != comment.user_id and current_user.id != 1 and current_user.id != comment.sar_call.user_id:
|
# Permission checks...
|
||||||
abort(403)
|
|
||||||
# Handle the form submission and save changes
|
|
||||||
if request.method == 'POST':
|
|
||||||
comment.text = request.form.get('text')
|
|
||||||
gpx_file = request.files.get('gpx_file')
|
|
||||||
if gpx_file:
|
|
||||||
comment.gpx_data = gpx_file.read().decode("utf-8")
|
|
||||||
db.session.commit()
|
|
||||||
return redirect(url_for('view_sar', sar_call_id=comment.sar_call_id))
|
|
||||||
|
|
||||||
|
comment_text = request.form.get('comment')
|
||||||
|
comment.text = comment_text
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
@app.route('/delete_comment/<int:comment_id>', methods=['POST'])
|
# return jsonify(success=True) # or return relevant response
|
||||||
|
return redirect(url_for('sar_details', id=comment.sar_call_id))
|
||||||
|
|
||||||
|
@app.route('/delete_comment/<int:id>', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def delete_comment(comment_id):
|
def delete_comment(id):
|
||||||
comment = Comment.query.get_or_404(comment_id)
|
comment = Comment.query.get_or_404(id)
|
||||||
if current_user.id != comment.user_id and current_user.id != 1 and current_user.id != comment.sar_call.user_id:
|
# if current_user.id != comment.user_id and current_user.id != 1 and current_user.id != comment.sar_call.user_id:
|
||||||
abort(403)
|
# abort(403)
|
||||||
db.session.delete(comment)
|
db.session.delete(comment)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('view_sar', sar_call_id=comment.sar_call_id))
|
flash('Comment deleted successfully!', 'success')
|
||||||
|
return redirect(url_for('sar_details', id=comment.sar_call_id))
|
||||||
|
@ -124,7 +124,7 @@
|
|||||||
{# });#}
|
{# });#}
|
||||||
{# map.addLayer(gpxLayer);#}
|
{# map.addLayer(gpxLayer);#}
|
||||||
{#})
|
{#})
|
||||||
;
|
|
||||||
#}
|
#}
|
||||||
|
|
||||||
// If editing, set the marker to the existing coordinates
|
// If editing, set the marker to the existing coordinates
|
||||||
|
@ -31,9 +31,10 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="finish_date">Finish Date:</label>
|
<label for="finish_date">Finish Date:</label>
|
||||||
<div>
|
<div>
|
||||||
<button type="button" id="today_button" class="btn btn-secondary">Today</button>
|
<button type="button" id="today_button" class="btn btn-secondary">Today</button>
|
||||||
</div>
|
</div>
|
||||||
<input type="date" name="finish_date" id="finish_date" class="form-control" value="{{ sar_call.finish_date }}" >
|
<input type="date" name="finish_date" id="finish_date" class="form-control"
|
||||||
|
value="{{ sar_call.finish_date }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -53,7 +54,7 @@
|
|||||||
<input type="text" name="latitude" class="form-control" value="{{ sar_call.latitude }}" required>
|
<input type="text" name="latitude" class="form-control" value="{{ sar_call.latitude }}" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="title">Title:</label>
|
<label for="title">Title:</label>
|
||||||
<input type="text" name="title" class="form-control" value="{{ sar_call.title }}" required>
|
<input type="text" name="title" class="form-control" value="{{ sar_call.title }}" required>
|
||||||
</div>
|
</div>
|
||||||
@ -69,11 +70,11 @@
|
|||||||
<textarea name="description_hidden" class="form-control">{{ sar_call.description_hidden }}</textarea>
|
<textarea name="description_hidden" class="form-control">{{ sar_call.description_hidden }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# <!-- GPX Track Fields (You can expand upon this based on the previous discussion about multiple tracks) -->#}
|
{# <!-- GPX Track Fields (You can expand upon this based on the previous discussion about multiple tracks) -->#}
|
||||||
{# <div class="form-group">#}
|
{# <div class="form-group">#}
|
||||||
{# <label for="gpx_file">Upload GPX Track:</label>#}
|
{# <label for="gpx_file">Upload GPX Track:</label>#}
|
||||||
{# <input type="file" name="gpx_file">#}
|
{# <input type="file" name="gpx_file">#}
|
||||||
{# </div>#}
|
{# </div>#}
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Update</button>
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
</form>
|
</form>
|
||||||
@ -103,16 +104,16 @@
|
|||||||
marker = L.marker([latInput.value, lngInput.value]).addTo(map);
|
marker = L.marker([latInput.value, lngInput.value]).addTo(map);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('today_button').addEventListener('click', function() {
|
document.getElementById('today_button').addEventListener('click', function () {
|
||||||
var today = new Date();
|
var today = new Date();
|
||||||
var dd = String(today.getDate()).padStart(2, '0');
|
var dd = String(today.getDate()).padStart(2, '0');
|
||||||
var mm = String(today.getMonth() + 1).padStart(2, '0'); // January is 0!
|
var mm = String(today.getMonth() + 1).padStart(2, '0'); // January is 0!
|
||||||
var yyyy = today.getFullYear();
|
var yyyy = today.getFullYear();
|
||||||
today = yyyy + '-' + mm + '-' + dd;
|
today = yyyy + '-' + mm + '-' + dd;
|
||||||
document.getElementById('finish_date').value = today;
|
document.getElementById('finish_date').value = today;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for sar in sar_calls %}
|
{% for sar in sar_calls %}
|
||||||
<tr class="clickable-row" data-href="{{ url_for('sar_details', id=sar.SARCall.id) }}">
|
<tr class="clickable-row" data-href="{{ url_for('sar_details', id=sar.SARCall.id) }}">
|
||||||
<td>{{ sar.SARCall.id }}</td>
|
<td>{{ sar.SARCall.id }}</td>
|
||||||
<td>{{ sar.SARCall.title }}</td>
|
<td>{{ sar.SARCall.title }}</td>
|
||||||
<td>{{ sar.SARCall.start_date }}</td>
|
<td>{{ sar.SARCall.start_date }}</td>
|
||||||
@ -48,11 +48,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.querySelectorAll('.clickable-row').forEach(row => {
|
document.querySelectorAll('.clickable-row').forEach(row => {
|
||||||
row.addEventListener('click', () => {
|
row.addEventListener('click', () => {
|
||||||
window.location.href = row.dataset.href;
|
window.location.href = row.dataset.href;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,66 +6,103 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
|
||||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
#map { height: 400px; }
|
#map {
|
||||||
.button { margin: 5px; }
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<h1 class="mb-4">SAR Job Details</h1>
|
<h1 class="mb-4">SAR Job Details</h1>
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p><strong>ID:</strong> {{ sar.id }}</p>
|
<p><strong>ID:</strong> {{ sar.id }}</p>
|
||||||
<p><strong>Start Date:</strong> {{ sar.start_date }}</p>
|
<p><strong>Start Date:</strong> {{ sar.start_date }}</p>
|
||||||
<p><strong>Manager:</strong> {{ sar.manager }}</p>
|
<p><strong>Manager:</strong> {{ sar.manager }}</p>
|
||||||
<!-- Other SAR details -->
|
<!-- Other SAR details -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="{{ url_for('edit_sar', id=sar.id) }}" class="btn btn-primary">Edit</a>
|
||||||
|
<a href="{{ url_for('delete_sar', id=sar.id) }}" class="btn btn-danger">Delete</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="map" class="mb-4"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Display Comments -->
|
||||||
|
{% for comment in sar.comments %}
|
||||||
|
<div class="comment">
|
||||||
|
<strong>{{ comment.user.username }}</strong>:
|
||||||
|
<p id="comment-text-{{ comment.id }}">{{ comment.text }}</p>
|
||||||
|
|
||||||
|
{% if comment.gpx_data %}
|
||||||
|
<!-- Display the GPX data on the map -->
|
||||||
|
{% endif %}
|
||||||
|
{% if current_user.id == comment.user_id or current_user.id == 1 or current_user.id == sar.user_id %}
|
||||||
|
<button class="edit-comment-btn" data-comment-id="{{ comment.id }}"
|
||||||
|
data-comment-text="{{ comment.text }}">Edit
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="delete-comment-btn">
|
||||||
|
<a href="{{ url_for('delete_comment', id=comment.id) }}">Delete</a>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- Edit Comment Modal -->
|
||||||
|
<div class="modal fade" id="editCommentModal" tabindex="-1" aria-labelledby="editCommentModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="editCommentModalLabel">Edit Comment</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="editCommentForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="commentText" class="col-form-label">Comment:</label>
|
||||||
|
<textarea class="form-control" id="commentText"></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="saveComment">Save changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Add Comment Form -->
|
||||||
|
<form action="{{ url_for('add_comment', sar_call_id=sar.id) }}" method="post" enctype="multipart/form-data">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="text">Comment:</label>
|
||||||
|
<textarea name="text" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="gpx_file">GPX File:</label>
|
||||||
|
<input type="file" name="gpx_file">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Add Comment</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="map" class="mb-4"></div>
|
|
||||||
<div>
|
|
||||||
<a href="{{ url_for('edit_sar', id=sar.id) }}" class="btn btn-primary">Edit</a>
|
|
||||||
<a href="{{ url_for('delete_sar', id=sar.id) }}" class="btn btn-danger">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Display Comments -->
|
|
||||||
{% for comment in sar_call.comments %}
|
|
||||||
<div class="comment">
|
|
||||||
<strong>{{ comment.user.username }}</strong>:
|
|
||||||
<p>{{ comment.text }}</p>
|
|
||||||
{% if comment.gpx_data %}
|
|
||||||
<!-- Display the GPX data on the map -->
|
|
||||||
{% endif %}
|
|
||||||
{% if current_user.id == comment.user_id or current_user.id == 1 or current_user.id == sar_call.user_id %}
|
|
||||||
<a href="{{ url_for('edit_comment', comment_id=comment.id) }}">Edit</a>
|
|
||||||
<a href="{{ url_for('delete_comment', comment_id=comment.id) }}">Delete</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<!-- Add Comment Form -->
|
|
||||||
<form action="{{ url_for('add_comment', sar_call_id=sar_call.id) }}" method="post" enctype="multipart/form-data">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="text">Comment:</label>
|
|
||||||
<textarea name="text" class="form-control"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="gpx_file">GPX File:</label>
|
|
||||||
<input type="file" name="gpx_file">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Add Comment</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var map = L.map('map').setView([{{ sar.latitude }}, {{ sar.longitude }}], 13);
|
var map = L.map('map').setView([{{ sar.latitude }}, {{ sar.longitude }}], 13);
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
@ -76,4 +113,40 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var commentId; // Declare this outside of the .edit-comment-btn click handler
|
||||||
|
var commentText; // Declare this outside of the .edit-comment-btn click handler
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('.edit-comment-btn').on('click', function () {
|
||||||
|
// Get the comment data
|
||||||
|
commentId = $(this).data('comment-id');
|
||||||
|
commentText = $(this).data('comment-text');
|
||||||
|
|
||||||
|
// Set the comment data in the modal
|
||||||
|
$('#commentText').val(commentText);
|
||||||
|
$('#editCommentModal').modal('show');
|
||||||
|
|
||||||
|
// Save changes
|
||||||
|
$('#saveComment').on('click', function () {
|
||||||
|
$.ajax({
|
||||||
|
url: '/edit_comment/' + commentId,
|
||||||
|
method: 'POST',
|
||||||
|
data: {comment: $('#commentText').val()},
|
||||||
|
success: function (response) {
|
||||||
|
// Update the comment display on the page
|
||||||
|
$('#comment-text-' + commentId).text( $('#commentText').val());
|
||||||
|
$('#editCommentModal').modal('hide');
|
||||||
|
// Update the comment display on the page as needed
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
// Handle error
|
||||||
|
alert("Error updating comment");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user