Compare commits
5 Commits
484c21eed9
...
b32359dbdf
Author | SHA1 | Date |
---|---|---|
![]() |
b32359dbdf | |
![]() |
350e306985 | |
![]() |
9bb74a84df | |
![]() |
bfd8796f2b | |
![]() |
77765d8605 |
|
@ -308,6 +308,8 @@ def view_transaction(id):
|
|||
transaction = None
|
||||
prev_id = None
|
||||
next_id = None
|
||||
prev_pending_id = None
|
||||
next_pending_id = None
|
||||
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
|
@ -325,6 +327,14 @@ def view_transaction(id):
|
|||
cur.execute('SELECT id FROM transactions WHERE id > %s ORDER BY id ASC LIMIT 1', (id,))
|
||||
next_result = cur.fetchone()
|
||||
next_id = next_result['id'] if next_result else None
|
||||
|
||||
cur.execute('SELECT id FROM transactions WHERE id < %s AND approved = FALSE ORDER BY id DESC LIMIT 1', (id,))
|
||||
prev_pending_result = cur.fetchone()
|
||||
prev_pending_id = prev_pending_result['id'] if prev_pending_result else None
|
||||
|
||||
cur.execute('SELECT id FROM transactions WHERE id > %s AND approved = FALSE ORDER BY id ASC LIMIT 1', (id,))
|
||||
next_pending_result = cur.fetchone()
|
||||
next_pending_id = next_pending_result['id'] if next_pending_result else None
|
||||
except Exception as e:
|
||||
logger.error(f"Database error: {e}")
|
||||
abort(404)
|
||||
|
@ -337,7 +347,7 @@ def view_transaction(id):
|
|||
if transaction is None:
|
||||
abort(404)
|
||||
|
||||
return render_template('view_transaction.html', transaction=transaction, documents=documents, prev_id=prev_id, next_id=next_id, version=VERSION, source = source)
|
||||
return render_template('view_transaction.html', transaction=transaction, documents=documents, prev_id=prev_id, next_id=next_id, prev_pending_id=prev_pending_id, next_pending_id=next_pending_id, version=VERSION, source = source)
|
||||
|
||||
@app.route('/document/<int:document_id>')
|
||||
def view_document(document_id):
|
||||
|
|
|
@ -4,11 +4,11 @@ import os
|
|||
def init_db():
|
||||
# Database connection parameters
|
||||
conn = psycopg2.connect(
|
||||
host="192.168.1.119",
|
||||
port=5433,
|
||||
dbname="testdb",
|
||||
user="testuser",
|
||||
password="testpass"
|
||||
host="db",
|
||||
port=5432,
|
||||
dbname="ploughshares",
|
||||
user="ploughshares",
|
||||
password="ploughshares_password"
|
||||
)
|
||||
conn.autocommit = True
|
||||
cursor = conn.cursor()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
-- Drop tables if they exist
|
||||
DROP TABLE IF EXISTS transaction_documents;
|
||||
DROP TABLE IF EXISTS transactions;
|
||||
DROP TABLE IF EXISTS transaction_documents CASCADE;
|
||||
DROP TABLE IF EXISTS transactions CASCADE;
|
||||
|
||||
-- Create transactions table
|
||||
CREATE TABLE IF NOT EXISTS transactions (
|
||||
|
|
|
@ -2,6 +2,23 @@
|
|||
|
||||
{% block title %}Transactions Pending Approval - Project Ploughshares{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<style>
|
||||
.items-selected {
|
||||
display: flex;
|
||||
background-color: var(--ploughshares-blue);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.items-selected p {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="card">
|
||||
|
@ -17,6 +34,15 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="items-selected">
|
||||
<p><span>0</span> items selected</p>
|
||||
<button class="approve">approve</button>
|
||||
<button class="reject">reject</button>
|
||||
<dialog class="reject">
|
||||
<p>reject records? this will delete them from the database permanently.</p>
|
||||
<button>delete permanently</button>
|
||||
</dialog>
|
||||
</div>
|
||||
{% if transactions %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
|
@ -31,6 +57,7 @@
|
|||
<th>Created At</th>
|
||||
<th>Documents</th>
|
||||
<th>Actions</th>
|
||||
<th>Select</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -120,6 +147,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><input type="checkbox" class="transaction-checkbox" data-transaction-id="{{ transaction['id'] }}"></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -137,6 +165,55 @@
|
|||
|
||||
{% block scripts %}
|
||||
<script nonce="{{ csp_nonce() }}">
|
||||
const checkedTransactions = [];
|
||||
const counter = document.querySelector(".items-selected span");
|
||||
|
||||
function handleCheck(transactionId, checked) {
|
||||
const index = checkedTransactions.findIndex(x=>x===transactionId);
|
||||
if (index >= 0) checkedTransactions.splice(index, 1);
|
||||
if (checked) checkedTransactions.push(transactionId);
|
||||
counter.textContent = checkedTransactions.length;
|
||||
}
|
||||
|
||||
const approveButton = document.querySelector(".items-selected button.approve");
|
||||
const rejectButton = document.querySelector(".items-selected button.reject");
|
||||
const rejectDialog = document.querySelector(".items-selected dialog.reject");
|
||||
const rejectDialogButton = document.querySelector(".items-selected dialog.reject button");
|
||||
|
||||
approveButton.addEventListener("click", async () => {
|
||||
const promises = checkedTransactions.map(async id=>{
|
||||
await fetch(
|
||||
`/api/transaction/${id}/approve`,
|
||||
{
|
||||
method: "POST",
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
location.reload();
|
||||
});
|
||||
|
||||
rejectButton.addEventListener("click", () => rejectDialog.showModal());
|
||||
|
||||
rejectDialog.addEventListener("click", e => {
|
||||
if (e.target === rejectDialog) rejectDialog.close();
|
||||
});
|
||||
|
||||
rejectDialogButton.addEventListener("click", async () => {
|
||||
const promises = checkedTransactions.map(async id=>{
|
||||
await fetch(
|
||||
`/api/transaction/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
location.reload();
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Refresh button functionality
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
|
@ -189,6 +266,15 @@
|
|||
});
|
||||
});
|
||||
|
||||
// Attach event listeners to checkboxes
|
||||
document.querySelectorAll('.transaction-checkbox').forEach(function(checkbox) {
|
||||
const transactionId = checkbox.getAttribute("data-transaction-id");
|
||||
handleCheck(transactionId, checkbox.checked);
|
||||
checkbox.addEventListener('change', e => {
|
||||
handleCheck(transactionId, e.target.checked);
|
||||
});
|
||||
});
|
||||
|
||||
// Helper function to check if user is typing in an input field
|
||||
function isUserTyping() {
|
||||
const activeElement = document.activeElement;
|
||||
|
|
|
@ -121,6 +121,28 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
{% if prev_pending_id %}
|
||||
<a href="{{ url_for('view_transaction', id=prev_pending_id) }}" class="btn btn-outline-primary" aria-label="Previous Pending transaction" title="Previous Pending Transaction">
|
||||
<i class="bi bi-arrow-left"></i> Previous Pending
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="btn btn-outline-secondary" disabled aria-label="No previous pending transaction" title="Previous Pending Transaction">
|
||||
<i class="bi bi-arrow-left"></i> Previous Pending
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if next_pending_id %}
|
||||
<a href="{{ url_for('view_transaction', id=next_pending_id) }}" class="btn btn-outline-primary" aria-label="Next Pending transaction" title="Next Pending Transaction">
|
||||
Next Pending <i class="bi bi-arrow-right"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="btn btn-outline-secondary" disabled aria-label="No next Pending transaction" title="Next Pending Transaction">
|
||||
Next Pending <i class="bi bi-arrow-right"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="details-tab" data-bs-toggle="tab" data-bs-target="#details" type="button" role="tab" aria-controls="details" aria-selected="true">Details</button>
|
||||
|
|
Loading…
Reference in New Issue