Compare commits
5 Commits
484c21eed9
...
b32359dbdf
Author | SHA1 | Date |
---|---|---|
![]() |
b32359dbdf | |
![]() |
350e306985 | |
![]() |
9bb74a84df | |
![]() |
bfd8796f2b | |
![]() |
77765d8605 |
|
@ -308,6 +308,8 @@ def view_transaction(id):
|
||||||
transaction = None
|
transaction = None
|
||||||
prev_id = None
|
prev_id = None
|
||||||
next_id = None
|
next_id = None
|
||||||
|
prev_pending_id = None
|
||||||
|
next_pending_id = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with conn.cursor() as cur:
|
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,))
|
cur.execute('SELECT id FROM transactions WHERE id > %s ORDER BY id ASC LIMIT 1', (id,))
|
||||||
next_result = cur.fetchone()
|
next_result = cur.fetchone()
|
||||||
next_id = next_result['id'] if next_result else None
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Database error: {e}")
|
logger.error(f"Database error: {e}")
|
||||||
abort(404)
|
abort(404)
|
||||||
|
@ -337,7 +347,7 @@ def view_transaction(id):
|
||||||
if transaction is None:
|
if transaction is None:
|
||||||
abort(404)
|
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>')
|
@app.route('/document/<int:document_id>')
|
||||||
def view_document(document_id):
|
def view_document(document_id):
|
||||||
|
|
|
@ -4,11 +4,11 @@ import os
|
||||||
def init_db():
|
def init_db():
|
||||||
# Database connection parameters
|
# Database connection parameters
|
||||||
conn = psycopg2.connect(
|
conn = psycopg2.connect(
|
||||||
host="192.168.1.119",
|
host="db",
|
||||||
port=5433,
|
port=5432,
|
||||||
dbname="testdb",
|
dbname="ploughshares",
|
||||||
user="testuser",
|
user="ploughshares",
|
||||||
password="testpass"
|
password="ploughshares_password"
|
||||||
)
|
)
|
||||||
conn.autocommit = True
|
conn.autocommit = True
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
-- Drop tables if they exist
|
-- Drop tables if they exist
|
||||||
DROP TABLE IF EXISTS transaction_documents;
|
DROP TABLE IF EXISTS transaction_documents CASCADE;
|
||||||
DROP TABLE IF EXISTS transactions;
|
DROP TABLE IF EXISTS transactions CASCADE;
|
||||||
|
|
||||||
-- Create transactions table
|
-- Create transactions table
|
||||||
CREATE TABLE IF NOT EXISTS transactions (
|
CREATE TABLE IF NOT EXISTS transactions (
|
||||||
|
|
|
@ -2,6 +2,23 @@
|
||||||
|
|
||||||
{% block title %}Transactions Pending Approval - Project Ploughshares{% endblock %}
|
{% 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 %}
|
{% block content %}
|
||||||
<div class="container-fluid mt-4">
|
<div class="container-fluid mt-4">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
@ -17,6 +34,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<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 %}
|
{% if transactions %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
|
@ -31,6 +57,7 @@
|
||||||
<th>Created At</th>
|
<th>Created At</th>
|
||||||
<th>Documents</th>
|
<th>Documents</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
|
<th>Select</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -120,6 +147,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td><input type="checkbox" class="transaction-checkbox" data-transaction-id="{{ transaction['id'] }}"></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -137,6 +165,55 @@
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script nonce="{{ csp_nonce() }}">
|
<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() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Refresh button functionality
|
// Refresh button functionality
|
||||||
const refreshBtn = document.getElementById('refreshBtn');
|
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
|
// Helper function to check if user is typing in an input field
|
||||||
function isUserTyping() {
|
function isUserTyping() {
|
||||||
const activeElement = document.activeElement;
|
const activeElement = document.activeElement;
|
||||||
|
|
|
@ -121,6 +121,28 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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">
|
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<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>
|
<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