diff --git a/instance/papers.db b/instance/papers.db
index e69de29..35b4507 100644
Binary files a/instance/papers.db and b/instance/papers.db differ
diff --git a/scipaperloader/templates/papers.html b/scipaperloader/templates/papers.html
new file mode 100644
index 0000000..a328b8f
--- /dev/null
+++ b/scipaperloader/templates/papers.html
@@ -0,0 +1,258 @@
+{% extends "base.html" %}
+{% block title %}Papers{% endblock %}
+{% block content %}
+
+{# --- Sort direction logic for each column --- #}
+{% set title_sort = 'asc' if sort_by != 'title' or sort_dir == 'desc' else 'desc' %}
+{% set journal_sort = 'asc' if sort_by != 'journal' or sort_dir == 'desc' else 'desc' %}
+{% set doi_sort = 'asc' if sort_by != 'doi' or sort_dir == 'desc' else 'desc' %}
+{% set issn_sort = 'asc' if sort_by != 'issn' or sort_dir == 'desc' else 'desc' %}
+{% set status_sort = 'asc' if sort_by != 'status' or sort_dir == 'desc' else 'desc' %}
+{% set created_sort = 'asc' if sort_by != 'created_at' or sort_dir == 'desc' else 'desc' %}
+{% set updated_sort = 'asc' if sort_by != 'updated_at' or sort_dir == 'desc' else 'desc' %}
+
+
+ {% for key, value in paper.__dict__.items() %}
+ {% if not key.startswith('_') and key != 'metadata' %}
+
{{ key.replace('_', ' ').capitalize() }}: {{ value }}
+ {% endif %}
+ {% endfor %}
+
+
\ No newline at end of file
diff --git a/scipaperloader/views.py b/scipaperloader/views.py
index 5fcc419..0a7c7f6 100644
--- a/scipaperloader/views.py
+++ b/scipaperloader/views.py
@@ -1,9 +1,13 @@
-from flask import Blueprint, render_template, current_app, request, flash, redirect, url_for
+from flask import Blueprint, render_template, current_app, request, flash, redirect, url_for, send_file
from .models import ScheduleConfig, VolumeConfig, PaperMetadata
from .db import db
import pandas as pd
from io import StringIO
import codecs
+import datetime
+import io
+import csv
+from sqlalchemy import asc, desc
bp = Blueprint('main', __name__)
@@ -12,6 +16,7 @@ bp = Blueprint('main', __name__)
def index():
return render_template("index.html")
+
REQUIRED_COLUMNS = {"alternative_id", "journal", "doi", "issn", "title"}
@@ -53,7 +58,7 @@ def upload():
type=row.get('type'),
language=row.get('language'),
published_online=parse_date(row.get('published_online')),
- status=None,
+ status="New",
file_path=None,
error_msg=None
)
@@ -70,10 +75,126 @@ def upload():
return render_template('upload.html')
+@bp.route('/papers')
+def list_papers():
+ page = request.args.get('page', 1, type=int)
+ per_page = 50
-@bp.route("/papers")
-def papers():
- return render_template("papers.html", app_title="PaperScraper")
+ # Filters
+ status = request.args.get('status')
+ created_from = request.args.get('created_from')
+ created_to = request.args.get('created_to')
+ updated_from = request.args.get('updated_from')
+ updated_to = request.args.get('updated_to')
+ sort_by = request.args.get('sort_by', 'created_at')
+ sort_dir = request.args.get('sort_dir', 'desc')
+
+ query = PaperMetadata.query
+
+ # Apply filters
+ if status:
+ query = query.filter(PaperMetadata.status == status)
+
+ def parse_date(val):
+ from datetime import datetime
+ try:
+ return datetime.strptime(val, '%Y-%m-%d')
+ except (ValueError, TypeError):
+ return None
+
+ if created_from := parse_date(created_from):
+ query = query.filter(PaperMetadata.created_at >= created_from)
+ if created_to := parse_date(created_to):
+ query = query.filter(PaperMetadata.created_at <= created_to)
+ if updated_from := parse_date(updated_from):
+ query = query.filter(PaperMetadata.updated_at >= updated_from)
+ if updated_to := parse_date(updated_to):
+ query = query.filter(PaperMetadata.updated_at <= updated_to)
+
+ # Sorting
+ sort_col = getattr(PaperMetadata, sort_by, PaperMetadata.created_at)
+ sort_func = desc if sort_dir == 'desc' else asc
+ query = query.order_by(sort_func(sort_col))
+
+ # Pagination
+ pagination = query.paginate(page=page, per_page=per_page, error_out=False)
+
+ # Statistics
+ total_papers = PaperMetadata.query.count()
+ status_counts = (
+ db.session.query(PaperMetadata.status, db.func.count(PaperMetadata.status))
+ .group_by(PaperMetadata.status)
+ .all()
+ )
+ status_counts = {status: count for status, count in status_counts}
+
+ return render_template(
+ 'papers.html',
+ papers=pagination.items,
+ pagination=pagination,
+ total_papers=total_papers,
+ status_counts=status_counts,
+ sort_by=sort_by,
+ sort_dir=sort_dir,
+ )
+
+
+@bp.route('/papers/export')
+def export_papers():
+ query = PaperMetadata.query
+
+ # Filters
+ status = request.args.get('status')
+ created_from = request.args.get('created_from')
+ created_to = request.args.get('created_to')
+ updated_from = request.args.get('updated_from')
+ updated_to = request.args.get('updated_to')
+ sort_by = request.args.get('sort_by', 'created_at')
+ sort_dir = request.args.get('sort_dir', 'desc')
+
+ query = PaperMetadata.query
+
+ # Apply filters
+ if status:
+ query = query.filter(PaperMetadata.status == status)
+
+ def parse_date(val):
+ try:
+ return datetime.datetime.strptime(val, "%Y-%m-%d")
+ except Exception:
+ return None
+
+ output = io.StringIO()
+ writer = csv.writer(output)
+ writer.writerow(['ID', 'Title', 'Journal', 'DOI', 'ISSN',
+ 'Status', 'Created At', 'Updated At'])
+
+ for paper in query:
+ writer.writerow([
+ paper.id,
+ paper.title,
+ getattr(paper, 'journal', ''),
+ paper.doi,
+ paper.issn,
+ paper.status,
+ paper.created_at,
+ paper.updated_at
+ ])
+
+ output.seek(0)
+ return send_file(io.BytesIO(output.read().encode('utf-8')),
+ mimetype='text/csv',
+ as_attachment=True,
+ download_name='papers.csv')
+
+ from flask import jsonify, render_template
+
+
+@bp.route('/papers/