From 1c84db947c0b82356c701005de350f5fa82762d5 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 4 Jul 2025 23:19:49 -0400 Subject: [PATCH] Improved usability and accessibility: - Added keyboard support for approval modals (Enter key) - Removed unnecessary keyboard shortcuts button - Fixed database schema issues - Added automatic approver name - Enhanced approval modals with document viewing - Version bump to 0.4.0 --- VERSION | 2 +- docker-compose.yml | 2 +- docker/ploughshares/Dockerfile | 1 + .../__pycache__/app.cpython-39.pyc | Bin 0 -> 19847 bytes .../migrate_approval.cpython-39.pyc | Bin 0 -> 2044 bytes docker/ploughshares/app.py | 111 +++++++++-- docker/ploughshares/populate_test_data.py | 20 +- docker/ploughshares/run_migration.py | 20 ++ docker/ploughshares/static/css/hotkeys.css | 19 -- docker/ploughshares/static/js/hotkeys.js | 21 --- docker/ploughshares/templates/api_docs.html | 8 +- .../templates/pending_approval.html | 73 +++++++- .../templates/view_transaction.html | 176 +++++++++++++++++- version_history.log | 1 + 14 files changed, 372 insertions(+), 82 deletions(-) create mode 100644 docker/ploughshares/__pycache__/app.cpython-39.pyc create mode 100644 docker/ploughshares/__pycache__/migrate_approval.cpython-39.pyc create mode 100755 docker/ploughshares/run_migration.py diff --git a/VERSION b/VERSION index 9e11b32..1d0ba9e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.1 +0.4.0 diff --git a/docker-compose.yml b/docker-compose.yml index 3b0a1d8..d4009ed 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: - POSTGRES_DB=ploughshares - POSTGRES_USER=ploughshares - POSTGRES_PASSWORD=ploughshares_password - - APP_VERSION=0.2.0 + - APP_VERSION=0.4.0 - APP_ENV=development depends_on: db: diff --git a/docker/ploughshares/Dockerfile b/docker/ploughshares/Dockerfile index 78cc2b6..bd33349 100644 --- a/docker/ploughshares/Dockerfile +++ b/docker/ploughshares/Dockerfile @@ -17,6 +17,7 @@ RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY app.py . COPY schema.sql . +COPY migrate_approval.py . COPY templates/ ./templates/ COPY static/ ./static/ # Tests directory is empty or doesn't contain required files diff --git a/docker/ploughshares/__pycache__/app.cpython-39.pyc b/docker/ploughshares/__pycache__/app.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89fa38d29bad76301e241a689bb30804d17b9902 GIT binary patch literal 19847 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') def view_transaction(id): + """ + View a transaction + """ conn = get_db_connection() if conn is None: flash("Database connection error", "error") @@ -279,28 +308,61 @@ def view_transaction(id): cur.execute('SELECT * FROM transactions WHERE id = %s', (id,)) transaction = cur.fetchone() - if transaction: - # Get previous transaction ID - cur.execute('SELECT id FROM transactions WHERE id < %s ORDER BY id DESC LIMIT 1', (id,)) - prev_result = cur.fetchone() - if prev_result: - prev_id = prev_result['id'] + if transaction is None: + abort(404) - # Get next transaction ID - cur.execute('SELECT id FROM transactions WHERE id > %s ORDER BY id ASC LIMIT 1', (id,)) - next_result = cur.fetchone() - if next_result: - next_id = next_result['id'] + # Get previous and next transaction IDs + cur.execute('SELECT id FROM transactions WHERE id < %s ORDER BY id DESC LIMIT 1', (id,)) + prev_result = cur.fetchone() + prev_id = prev_result['id'] if prev_result else None + + 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 except Exception as e: logger.error(f"Database error: {e}") - flash(f"Database error: {e}", "error") + abort(404) finally: conn.close() + # Get documents for this transaction + documents = get_transaction_documents(id) + if transaction is None: abort(404) - return render_template('view_transaction.html', transaction=transaction, prev_id=prev_id, next_id=next_id, version=VERSION) + return render_template('view_transaction.html', transaction=transaction, documents=documents, prev_id=prev_id, next_id=next_id, version=VERSION) + +@app.route('/document/') +def view_document(document_id): + """ + View a document + """ + conn = get_db_connection() + if conn is None: + flash("Database connection error", "error") + abort(404) + + document = None + try: + with conn.cursor() as cur: + cur.execute('SELECT * FROM transaction_documents WHERE document_id = %s', (document_id,)) + document = cur.fetchone() + + if document is None: + abort(404) + except Exception as e: + logger.error(f"Database error: {e}") + abort(404) + finally: + conn.close() + + if document is None: + abort(404) + + # Serve the file from the uploads directory + file_path = os.path.join(app.config['UPLOAD_FOLDER'], document['file_path']) + return send_from_directory(os.path.dirname(file_path), os.path.basename(file_path), as_attachment=False) @app.route('/pending-approval') def pending_approval(): @@ -312,10 +374,19 @@ def pending_approval(): flash("Database connection error", "error") return render_template('pending_approval.html', transactions=[], version=VERSION) + transactions = [] try: with conn.cursor() as cur: cur.execute('SELECT * FROM transactions WHERE approved = FALSE ORDER BY id DESC') transactions = cur.fetchall() + + # For each transaction, count the number of documents + for transaction in transactions: + cur.execute('SELECT COUNT(*) as doc_count FROM transaction_documents WHERE transaction_id = %s', + (transaction['id'],)) + doc_count_result = cur.fetchone() + transaction['document_count'] = doc_count_result['doc_count'] if doc_count_result else 0 + except Exception as e: logger.error(f"Database error: {e}") flash(f"Database error: {e}", "error") @@ -337,8 +408,8 @@ def approve_transaction(id): try: with conn.cursor() as cur: - # Get the approver name from the form or use default - approver = request.form.get('approver', 'System') + # Use a default approver name instead of requiring user input + approver = "System Administrator" # Update the transaction cur.execute( @@ -402,9 +473,8 @@ def api_approve_transaction(id): if conn is None: return jsonify({"error": "Database connection error"}), 500 - # Get the approver from the request data or use default - data = request.get_json() if request.is_json else {} - approver = data.get('approver', 'API') + # Use the same default approver name for consistency + approver = "System Administrator" try: with conn.cursor() as cur: @@ -644,6 +714,11 @@ def bootstrap_database(): logger.error("Could not get count from database") except Exception as count_error: logger.error(f"Error counting transactions: {count_error}") + + # Run database migrations to ensure all columns exist + logger.info(f"Ploughshares v{VERSION} - Running database migrations...") + migrate_database() + logger.info(f"Ploughshares v{VERSION} - Database migrations completed.") except Exception as e: logger.error(f"Error checking database: {e}") finally: diff --git a/docker/ploughshares/populate_test_data.py b/docker/ploughshares/populate_test_data.py index aa8d95c..a4a710f 100644 --- a/docker/ploughshares/populate_test_data.py +++ b/docker/ploughshares/populate_test_data.py @@ -1,8 +1,16 @@ import os import psycopg2 import json +import logging from datetime import datetime +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger('ploughshares') + def get_db_connection(): host = os.environ.get('POSTGRES_HOST', 'db') port = os.environ.get('POSTGRES_PORT', '5432') @@ -31,15 +39,15 @@ def populate_test_data(): # Extract the transactions array if 'transactions' not in data_json: - print("Error: 'transactions' key not found in data.json") + logger.error("'transactions' key not found in data.json") return data = data_json['transactions'] except FileNotFoundError: - print("Error: data.json file not found") + logger.error("data.json file not found") return except json.JSONDecodeError: - print("Error: data.json is not valid JSON") + logger.error("data.json is not valid JSON") return # Insert sample transactions @@ -75,14 +83,14 @@ def populate_test_data(): item.get('contract_number', ''), item.get('comments', '') )) - print(f"Added transaction: {item.get('company_division', 'Unknown')} - {item.get('recipient', 'Unknown')}") + logger.info(f"Added transaction: {item.get('company_division', 'Unknown')} - {item.get('recipient', 'Unknown')}") except Exception as e: - print(f"Error inserting transaction: {e}") + logger.error(f"Error inserting transaction: {e}") cursor.close() conn.close() - print("Database populated with test data successfully.") + logger.info("Database populated with test data successfully.") if __name__ == "__main__": populate_test_data() \ No newline at end of file diff --git a/docker/ploughshares/run_migration.py b/docker/ploughshares/run_migration.py new file mode 100755 index 0000000..0297e4a --- /dev/null +++ b/docker/ploughshares/run_migration.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +""" +Run this script to manually apply database migrations to add the 'approved' column +to the transactions table if it doesn't exist. +""" + +import logging +from migrate_approval import migrate_database + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger('ploughshares') + +if __name__ == "__main__": + logger.info("Starting manual database migration...") + migrate_database() + logger.info("Database migration completed.") \ No newline at end of file diff --git a/docker/ploughshares/static/css/hotkeys.css b/docker/ploughshares/static/css/hotkeys.css index 0214cf9..0dce9e3 100644 --- a/docker/ploughshares/static/css/hotkeys.css +++ b/docker/ploughshares/static/css/hotkeys.css @@ -16,20 +16,6 @@ kbd { box-shadow: inset 0 -0.1rem 0 rgba(0, 0, 0, 0.25); } -/* Hotkey reference button */ -.hotkey-help-btn { - position: fixed; - bottom: 20px; - right: 20px; - z-index: 1030; - opacity: 0.7; - transition: opacity 0.3s; -} - -.hotkey-help-btn:hover { - opacity: 1; -} - /* Tooltip for hotkeys */ [data-hotkey]::after { content: attr(data-hotkey); @@ -64,11 +50,6 @@ kbd { /* Responsive adjustments */ @media (max-width: 768px) { - .hotkey-help-btn { - bottom: 10px; - right: 10px; - } - [data-hotkey]::after { display: none; } diff --git a/docker/ploughshares/static/js/hotkeys.js b/docker/ploughshares/static/js/hotkeys.js index 98f3014..7cd0238 100644 --- a/docker/ploughshares/static/js/hotkeys.js +++ b/docker/ploughshares/static/js/hotkeys.js @@ -348,27 +348,6 @@ function addHotkeyTooltips() { // Get the current page based on URL const currentPath = window.location.pathname; - // Add tooltips for global elements - const helpLink = document.createElement('div'); - helpLink.className = 'position-fixed bottom-0 end-0 p-3'; - helpLink.innerHTML = ` - - `; - document.body.appendChild(helpLink); - - // Add click event listener directly - document.addEventListener('DOMContentLoaded', function() { - const hotkeyHelpButton = document.getElementById('hotkeyHelpButton'); - if (hotkeyHelpButton) { - hotkeyHelpButton.addEventListener('click', function() { - showHotkeyReference(); - }); - } - }); - // Page-specific tooltips if (currentPath === '/' || currentPath === '/index.html') { // Transaction list page diff --git a/docker/ploughshares/templates/api_docs.html b/docker/ploughshares/templates/api_docs.html index 390bd76..d748a0a 100644 --- a/docker/ploughshares/templates/api_docs.html +++ b/docker/ploughshares/templates/api_docs.html @@ -195,15 +195,11 @@

Important: This endpoint provides human-in-the-loop verification. All transactions (especially those created via API) require explicit approval before being considered valid. - The approver name is recorded for audit purposes. + The system automatically records the approval with a standard identifier.

Complete Example:
-
curl -X POST "http://{{ server_name }}/api/transaction/2/approve" \
-  -H "Content-Type: application/json" \
-  -d '{
-    "approver": "John Doe"
-  }'
+
curl -X POST "http://{{ server_name }}/api/transaction/2/approve"
Response:
diff --git a/docker/ploughshares/templates/pending_approval.html b/docker/ploughshares/templates/pending_approval.html index 3579d76..4e42114 100644 --- a/docker/ploughshares/templates/pending_approval.html +++ b/docker/ploughshares/templates/pending_approval.html @@ -29,6 +29,7 @@ Source Date Recipient Created At + Documents Actions @@ -42,6 +43,13 @@ {{ transaction['source_date'].strftime('%Y-%m-%d') if transaction['source_date'] else 'N/A' }} {{ transaction['recipient'] }} {{ transaction['created_at'].strftime('%Y-%m-%d %H:%M') if transaction['created_at'] else 'N/A' }} + + {% if transaction['document_count'] > 0 %} + {{ transaction['document_count'] }} + {% else %} + 0 + {% endif %} +
@@ -57,7 +65,7 @@