diff --git a/app/routes/documents.py b/app/routes/documents.py
index 3a18a6c..1960312 100644
--- a/app/routes/documents.py
+++ b/app/routes/documents.py
@@ -233,7 +233,7 @@ def list_documents(request: Request, db: Session = Depends(get_db)):
return templates.TemplateResponse(
request=request,
name="documents/list.html",
- context={"request": request, "documents": documents},
+ context={"request": request, "documents": documents, "active_page": "documents"},
)
@@ -507,5 +507,6 @@ def document_detail(document_id: str, request: Request, queue: str | None = None
"error_actual": error_actual,
"extracted_form": extracted_form,
"current_extracted": current_extracted,
+ "active_page": "documents",
},
)
diff --git a/app/routes/ingest.py b/app/routes/ingest.py
index bce03fd..98cdb7a 100644
--- a/app/routes/ingest.py
+++ b/app/routes/ingest.py
@@ -23,6 +23,7 @@ def ingest_home(request: Request):
context={
"request": request,
"inbox_root": INBOX_ROOT,
+ "active_page": "ingest",
},
)
diff --git a/app/routes/line_items.py b/app/routes/line_items.py
new file mode 100644
index 0000000..3626dea
--- /dev/null
+++ b/app/routes/line_items.py
@@ -0,0 +1,298 @@
+from pathlib import Path
+from decimal import Decimal, InvalidOperation
+
+from fastapi import APIRouter, Depends, Form, Query, Request
+from fastapi.responses import HTMLResponse, RedirectResponse
+from fastapi.templating import Jinja2Templates
+from sqlalchemy import func
+from sqlalchemy.orm import Session, selectinload
+
+from app.db.deps import get_db
+from app.logic.extraction import get_current_extracted_fields
+from app.models.document import Document
+from app.models.receipt_line_item import ReceiptLineItem
+
+router = APIRouter(prefix="/line-items", tags=["line-items"])
+
+BASE_DIR = Path(__file__).resolve().parent.parent
+templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
+
+
+def _decimal_to_str(value: Decimal | None) -> str:
+ if value is None:
+ return ""
+ return str(value)
+
+
+def _to_decimal(value: str | None) -> Decimal | None:
+ if value is None:
+ return None
+ cleaned = str(value).strip()
+ if not cleaned:
+ return None
+ try:
+ return Decimal(cleaned)
+ except (InvalidOperation, TypeError):
+ return None
+
+
+def _line_item_quality_rating(item: ReceiptLineItem) -> str:
+ extra = item.extra_json or {}
+ value = extra.get("quality_rating")
+ return "" if value is None else str(value)
+
+
+def _line_item_quality_note(item: ReceiptLineItem) -> str:
+ extra = item.extra_json or {}
+ value = extra.get("quality_note")
+ return "" if value is None else str(value)
+
+
+@router.post("/{line_item_id}/review", response_class=RedirectResponse)
+def save_line_item_review(
+ line_item_id: int,
+ q: str = Form(""),
+ merchant: str = Form(""),
+ category: str = Form(""),
+ date_from: str = Form(""),
+ date_to: str = Form(""),
+ rating_min: str = Form(""),
+ rating_max: str = Form(""),
+ quality_rating: str = Form(""),
+ quality_note: str = Form(""),
+ db: Session = Depends(get_db),
+):
+ item = db.query(ReceiptLineItem).filter(ReceiptLineItem.id == line_item_id).first()
+ if item is None:
+ return RedirectResponse(url="/line-items/", status_code=303)
+
+ extra = dict(item.extra_json or {})
+
+ rating_clean = quality_rating.strip()
+ note_clean = quality_note.strip()
+
+ if rating_clean:
+ extra["quality_rating"] = rating_clean
+ else:
+ extra.pop("quality_rating", None)
+
+ if note_clean:
+ extra["quality_note"] = note_clean
+ else:
+ extra.pop("quality_note", None)
+
+ item.extra_json = extra
+ db.commit()
+
+ redirect_url = (
+ f"/line-items/?q={q}&merchant={merchant}&category={category}"
+ f"&date_from={date_from}&date_to={date_to}"
+ f"&rating_min={rating_min}&rating_max={rating_max}"
+ )
+ return RedirectResponse(url=redirect_url, status_code=303)
+
+
+@router.get("/", response_class=HTMLResponse)
+def list_line_items(
+ request: Request,
+ q: str = Query("", description="Item description contains"),
+ merchant: str = Query("", description="Merchant contains"),
+ category: str = Query("", description="Category equals"),
+ date_from: str = Query("", description="YYYY-MM-DD"),
+ date_to: str = Query("", description="YYYY-MM-DD"),
+ rating_min: str = Query("", description="Minimum rating"),
+ rating_max: str = Query("", description="Maximum rating"),
+ db: Session = Depends(get_db),
+):
+ items = (
+ db.query(ReceiptLineItem)
+ .options(
+ selectinload(ReceiptLineItem.document).selectinload(Document.extracted_fields)
+ )
+ .order_by(ReceiptLineItem.id.desc())
+ .all()
+ )
+
+ q_norm = q.strip().lower()
+ merchant_norm = merchant.strip().lower()
+ category_norm = category.strip().lower()
+ rating_min_dec = _to_decimal(rating_min)
+ rating_max_dec = _to_decimal(rating_max)
+
+ rows: list[dict] = []
+
+ for item in items:
+ document = item.document
+ if document is None:
+ continue
+
+ extracted = get_current_extracted_fields(document)
+ merchant_value = ""
+ transaction_date = ""
+
+ if extracted is not None:
+ merchant_value = (
+ extracted.merchant_normalized
+ or extracted.merchant_raw
+ or ""
+ )
+ if extracted.transaction_date:
+ transaction_date = extracted.transaction_date.isoformat()
+
+ if not transaction_date and document.created_at:
+ transaction_date = document.created_at.date().isoformat()
+
+ description_value = (
+ item.normalized_description
+ or item.raw_description
+ or ""
+ )
+ category_value = item.item_category or ""
+ quality_rating_value = _line_item_quality_rating(item)
+ quality_note_value = _line_item_quality_note(item)
+ quality_rating_dec = _to_decimal(quality_rating_value)
+
+ if q_norm and q_norm not in description_value.lower():
+ continue
+ if merchant_norm and merchant_norm not in merchant_value.lower():
+ continue
+ if category_norm and category_norm not in category_value.lower():
+ continue
+ if date_from and (not transaction_date or transaction_date < date_from):
+ continue
+ if date_to and (not transaction_date or transaction_date > date_to):
+ continue
+ if rating_min_dec is not None:
+ if quality_rating_dec is None or quality_rating_dec < rating_min_dec:
+ continue
+ if rating_max_dec is not None:
+ if quality_rating_dec is None or quality_rating_dec > rating_max_dec:
+ continue
+
+ rows.append(
+ {
+ "line_item_id": item.id,
+ "document_id": document.document_id,
+ "transaction_date": transaction_date,
+ "merchant": merchant_value,
+ "description": description_value,
+ "raw_description": item.raw_description or "",
+ "quantity": _decimal_to_str(item.quantity),
+ "line_total": _decimal_to_str(item.line_total),
+ "category": category_value,
+ "confidence": _decimal_to_str(item.confidence),
+ "quality_rating": quality_rating_value,
+ "quality_note": quality_note_value,
+ }
+ )
+
+ rows.sort(
+ key=lambda row: (
+ row["transaction_date"] or "",
+ row["merchant"] or "",
+ row["description"] or "",
+ ),
+ reverse=True,
+ )
+
+ return templates.TemplateResponse(
+ request=request,
+ name="line_items/list.html",
+ context={
+ "request": request,
+ "rows": rows,
+ "q": q,
+ "merchant": merchant,
+ "category": category,
+ "date_from": date_from,
+ "date_to": date_to,
+ "rating_min": rating_min,
+ "rating_max": rating_max,
+ "active_page": "line_items",
+ },
+ )
+
+
+@router.get("/summary", response_class=HTMLResponse)
+def summarize_line_items(
+ request: Request,
+ q: str = Query("", description="Item contains"),
+ db: Session = Depends(get_db),
+):
+ query = (
+ db.query(
+ ReceiptLineItem.normalized_description.label("item"),
+ func.count().label("count"),
+ func.avg(ReceiptLineItem.line_total).label("avg_price"),
+ func.min(ReceiptLineItem.line_total).label("min_price"),
+ func.max(ReceiptLineItem.line_total).label("max_price"),
+ )
+ )
+
+ if q:
+ query = query.filter(
+ ReceiptLineItem.normalized_description.ilike(f"%{q}%")
+ )
+
+ query = query.group_by(ReceiptLineItem.normalized_description)
+ results = query.all()
+
+ rating_query = db.query(
+ ReceiptLineItem.normalized_description,
+ ReceiptLineItem.extra_json,
+ )
+ if q:
+ rating_query = rating_query.filter(
+ ReceiptLineItem.normalized_description.ilike(f"%{q}%")
+ )
+ rating_rows = rating_query.all()
+
+ rating_map: dict[str, dict[str, Decimal | int]] = {}
+
+ for item_name, extra_json in rating_rows:
+ key = item_name or ""
+ rating_info = rating_map.setdefault(
+ key,
+ {"rated_count": 0, "rating_sum": Decimal("0")}
+ )
+ extra = extra_json or {}
+ rating_dec = _to_decimal(extra.get("quality_rating"))
+ if rating_dec is not None:
+ rating_info["rated_count"] += 1
+ rating_info["rating_sum"] += rating_dec
+
+ rows = []
+ for r in results:
+ item_name = r.item or ""
+ rating_info = rating_map.get(item_name, {"rated_count": 0, "rating_sum": Decimal("0")})
+ rated_count = int(rating_info["rated_count"])
+ rating_sum = rating_info["rating_sum"]
+
+ avg_rating = ""
+ if rated_count > 0:
+ avg_rating = str((rating_sum / rated_count).quantize(Decimal("0.01")))
+
+ rows.append(
+ {
+ "item": item_name,
+ "count": r.count,
+ "avg_price": str(round(r.avg_price, 2)) if r.avg_price is not None else "",
+ "min_price": str(r.min_price) if r.min_price is not None else "",
+ "max_price": str(r.max_price) if r.max_price is not None else "",
+ "rated_count": rated_count,
+ "avg_rating": avg_rating,
+ }
+ )
+
+ rows.sort(key=lambda x: (x["count"], x["item"]), reverse=True)
+
+ return templates.TemplateResponse(
+ request=request,
+ name="line_items/summary.html",
+ context={
+ "request": request,
+ "rows": rows,
+ "q": q,
+ "active_page": "line_item_summary",
+ },
+ )
diff --git a/app/routes/queue.py b/app/routes/queue.py
index 87855f2..f616f9e 100644
--- a/app/routes/queue.py
+++ b/app/routes/queue.py
@@ -54,5 +54,6 @@ def review_queue(request: Request, db: Session = Depends(get_db)):
"recently_updated": recently_updated,
"next_ocr": next_ocr,
"next_fields": next_fields,
+ "active_page": "queue",
},
)
diff --git a/app/routes/trash.py b/app/routes/trash.py
index 1457476..be1a342 100644
--- a/app/routes/trash.py
+++ b/app/routes/trash.py
@@ -30,7 +30,7 @@ def trash_index(request: Request, db: Session = Depends(get_db)):
return templates.TemplateResponse(
request=request,
name="trash/index.html",
- context={"request": request, "documents": documents},
+ context={"request": request, "documents": documents, "active_page": "trash"},
)
diff --git a/app/templates/documents/detail.html b/app/templates/documents/detail.html
index 7daea7b..df1018c 100644
--- a/app/templates/documents/detail.html
+++ b/app/templates/documents/detail.html
@@ -7,22 +7,7 @@
-
+ {% include "partials/sidebar.html" %}
{% if error == "line_count_mismatch" %}
diff --git a/app/templates/documents/detail.html.bak_sidebar b/app/templates/documents/detail.html.bak_sidebar
new file mode 100644
index 0000000..a3293f6
--- /dev/null
+++ b/app/templates/documents/detail.html.bak_sidebar
@@ -0,0 +1,362 @@
+
+
+
+
+ {{ document.document_id }}
+
+
+
+
+
+
+
+ {% if error == "line_count_mismatch" %}
+
+ Could not save reviewed OCR because line count did not match OCR layout.
+ Expected {{ error_expected }}, got {{ error_actual }}.
+
+ {% elif error == "save_ocr_corrected_failed" %}
+
+ Could not save OCR-corrected PDF. Check that reviewed OCR line count matches raw OCR line count.
+
+ {% elif error == "rerun_ocr_failed" %}
+ OCR rerun failed.
+ {% elif error == "save_field_enriched_failed" %}
+ Could not save field-enriched PDF.
+ {% endif %}
+
+
+
+
+
+
+
Document preview
+ {% if file_url %}
+ {% if document.mime_type == "application/pdf" %}
+
+ {% elif document.mime_type in ["image/jpeg", "image/png"] %}
+

+ {% else %}
+
Open file
+ {% endif %}
+ {% else %}
+
No preview available.
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Reviewed OCR
+ {% if reviewed_ocr %}
+
Current reviewed version saved at {{ reviewed_ocr.created_at }} — v{{ reviewed_ocr.version_number }}
+ {% else %}
+
No reviewed OCR saved yet.
+ {% endif %}
+
+
+ Expected OCR lines: {{ expected_line_count }}
+ Current editor lines: {{ actual_line_count }}
+
+ Line count mismatch may affect corrected PDF layout.
+
+
+
+
+
+
+
+
+
+
Document versions
+ {% if document.versions %}
+
+
+
+
+ | Version |
+ Type |
+ Path |
+ Created |
+ Notes |
+
+
+
+ {% for version in document.versions %}
+
+ | v{{ version.version_number }} |
+ {{ version.version_type }} |
+ {{ version.file_path }} |
+ {{ version.created_at }} |
+ {{ version.notes or "" }} |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
No versions found.
+ {% endif %}
+
+
+
+
Raw OCR
+ {% if raw_ocr %}
+
+
Quality flags: {{ raw_ocr.quality_flags if raw_ocr and raw_ocr.quality_flags else [] }}
+
{{ raw_ocr.text_content }}
+ {% else %}
+
No raw OCR text found.
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/templates/documents/list.html b/app/templates/documents/list.html
index 53f13bd..4ea6e72 100644
--- a/app/templates/documents/list.html
+++ b/app/templates/documents/list.html
@@ -7,22 +7,7 @@
-
+ {% include "partials/sidebar.html" %}
diff --git a/app/templates/documents/list.html.bak_sidebar b/app/templates/documents/list.html.bak_sidebar
new file mode 100644
index 0000000..f0de017
--- /dev/null
+++ b/app/templates/documents/list.html.bak_sidebar
@@ -0,0 +1,95 @@
+
+
+
+
+
Documents
+
+
+
+
+
+
+
+
+
+
Documents
+
Active documents available for review and processing.
+
+
+
+
+
+
+
All documents
+ {% if documents %}
+
+
+
+
+ | Document |
+ Type |
+ Review status |
+ Current path |
+ Updated |
+
+
+
+ {% for doc in documents %}
+
+ | {{ doc.document_id }} |
+ {{ doc.document_type }} |
+ {{ doc.review_status }} |
+ {{ doc.current_path }} |
+ {{ doc.updated_at }} |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
No documents found.
+ {% endif %}
+
+
+
+
+
+
+
diff --git a/app/templates/ingest/index.html b/app/templates/ingest/index.html
index 5df07bd..7ef0510 100644
--- a/app/templates/ingest/index.html
+++ b/app/templates/ingest/index.html
@@ -7,22 +7,7 @@
-
+ {% include "partials/sidebar.html" %}
diff --git a/app/templates/ingest/index.html.bak_sidebar b/app/templates/ingest/index.html.bak_sidebar
new file mode 100644
index 0000000..83b4e29
--- /dev/null
+++ b/app/templates/ingest/index.html.bak_sidebar
@@ -0,0 +1,105 @@
+
+
+
+
+
Ingest
+
+
+
+
+
+
+
+
+
+
Ingest
+
Upload files or ingest from server-side paths.
+
+
+
+
+
Upload files
+
+
+
+
+
Server-side ingest
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/templates/line_items/list.html b/app/templates/line_items/list.html
new file mode 100644
index 0000000..1c0c761
--- /dev/null
+++ b/app/templates/line_items/list.html
@@ -0,0 +1,144 @@
+
+
+
+
+
Line Items
+
+
+
+
+ {% include "partials/sidebar.html" %}
+
+
+
+
+
Line Items
+
Search extracted purchase lines across documents
+
+
+
+
+
+
+
Results
+
+ {% if rows %}
+ {% for row in rows %}
+
+
+
+
{{ row.transaction_date }} · {{ row.merchant }}
+
{{ row.description }}
+
{{ row.raw_description }}
+
+
+ {% if row.category %}
+ {{ row.category }}
+ {% endif %}
+ {% if row.quantity %}
+ Qty {{ row.quantity }}
+ {% endif %}
+ {% if row.line_total %}
+ ${{ row.line_total }}
+ {% endif %}
+ {% if row.confidence %}
+ Conf {{ row.confidence }}
+ {% endif %}
+ {% if row.quality_rating %}
+ Rating {{ row.quality_rating }}
+ {% endif %}
+
+
+
+
+
+ {% endfor %}
+ {% else %}
+
No line items found for the current filters.
+ {% endif %}
+
+
+
+
+
+
+
diff --git a/app/templates/line_items/list.html.bak_sidebar b/app/templates/line_items/list.html.bak_sidebar
new file mode 100644
index 0000000..b1f9f4e
--- /dev/null
+++ b/app/templates/line_items/list.html.bak_sidebar
@@ -0,0 +1,161 @@
+
+
+
+
+
Line Items
+
+
+
+
+
+
+
+
+
+
Line Items
+
Search extracted purchase lines across documents
+
+
+
+
+
+
+
Results
+
+ {% if rows %}
+ {% for row in rows %}
+
+
+
+
{{ row.transaction_date }} · {{ row.merchant }}
+
{{ row.description }}
+
{{ row.raw_description }}
+
+
+ {% if row.category %}
+ {{ row.category }}
+ {% endif %}
+ {% if row.quantity %}
+ Qty {{ row.quantity }}
+ {% endif %}
+ {% if row.line_total %}
+ ${{ row.line_total }}
+ {% endif %}
+ {% if row.confidence %}
+ Conf {{ row.confidence }}
+ {% endif %}
+ {% if row.quality_rating %}
+ Rating {{ row.quality_rating }}
+ {% endif %}
+
+
+
+
+
+ {% endfor %}
+ {% else %}
+
No line items found for the current filters.
+ {% endif %}
+
+
+
+
+
+
+
diff --git a/app/templates/line_items/summary.html b/app/templates/line_items/summary.html
new file mode 100644
index 0000000..8a6f855
--- /dev/null
+++ b/app/templates/line_items/summary.html
@@ -0,0 +1,93 @@
+
+
+
+
+
Line Item Summary
+
+
+
+
+ {% include "partials/sidebar.html" %}
+
+
+
+
+
Line Item Summary
+
Aggregate prices and ratings across extracted line items
+
+
+
+
+
+
+
Summary Results
+
+ {% if rows %}
+
+
+
+
+ | Item |
+ Count |
+ Avg Price |
+ Min Price |
+ Max Price |
+ Rated Count |
+ Avg Rating |
+
+
+
+ {% for row in rows %}
+
+ | {{ row.item }} |
+ {{ row.count }} |
+ {{ row.avg_price }} |
+ {{ row.min_price }} |
+ {{ row.max_price }} |
+ {{ row.rated_count }} |
+ {{ row.avg_rating }} |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
No summary rows found for the current search.
+ {% endif %}
+
+
+
+
+
+
+
diff --git a/app/templates/line_items/summary.html.bak_sidebar b/app/templates/line_items/summary.html.bak_sidebar
new file mode 100644
index 0000000..79d358f
--- /dev/null
+++ b/app/templates/line_items/summary.html.bak_sidebar
@@ -0,0 +1,110 @@
+
+
+
+
+
Line Item Summary
+
+
+
+
+
+
+
+
+
+
Line Item Summary
+
Aggregate prices and ratings across extracted line items
+
+
+
+
+
+
+
Summary Results
+
+ {% if rows %}
+
+
+
+
+ | Item |
+ Count |
+ Avg Price |
+ Min Price |
+ Max Price |
+ Rated Count |
+ Avg Rating |
+
+
+
+ {% for row in rows %}
+
+ | {{ row.item }} |
+ {{ row.count }} |
+ {{ row.avg_price }} |
+ {{ row.min_price }} |
+ {{ row.max_price }} |
+ {{ row.rated_count }} |
+ {{ row.avg_rating }} |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
No summary rows found for the current search.
+ {% endif %}
+
+
+
+
+
+
+
diff --git a/app/templates/partials/sidebar.html b/app/templates/partials/sidebar.html
new file mode 100644
index 0000000..4552ffb
--- /dev/null
+++ b/app/templates/partials/sidebar.html
@@ -0,0 +1,18 @@
+
diff --git a/app/templates/queue/index.html b/app/templates/queue/index.html
index 0b17337..26806ce 100644
--- a/app/templates/queue/index.html
+++ b/app/templates/queue/index.html
@@ -7,22 +7,7 @@
-
+ {% include "partials/sidebar.html" %}
diff --git a/app/templates/queue/index.html.bak_sidebar b/app/templates/queue/index.html.bak_sidebar
new file mode 100644
index 0000000..3b97f40
--- /dev/null
+++ b/app/templates/queue/index.html.bak_sidebar
@@ -0,0 +1,134 @@
+
+
+
+
+
Review Queue
+
+
+
+
+
+
+
+
+
+
Review Queue
+
Work through OCR review and field extraction in order.
+
+
+
+
+
+
+
Needs OCR review ({{ needs_ocr_review|length }})
+ {% if needs_ocr_review %}
+
+
+ | Document | Type | Review status | Updated |
+
+ {% for doc in needs_ocr_review %}
+
+ | {{ doc.document_id }} |
+ {{ doc.document_type }} |
+ {{ doc.review_status }} |
+ {{ doc.updated_at }} |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
No documents currently need OCR review.
+ {% endif %}
+
+
+
+
Needs field extraction ({{ needs_field_extraction|length }})
+ {% if needs_field_extraction %}
+
+
+ | Document | Type | Review status | Updated |
+
+ {% for doc in needs_field_extraction %}
+
+ | {{ doc.document_id }} |
+ {{ doc.document_type }} |
+ {{ doc.review_status }} |
+ {{ doc.updated_at }} |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
No reviewed documents are waiting on field extraction.
+ {% endif %}
+
+
+
+
Recently updated
+ {% if recently_updated %}
+
+
+ | Document | Type | Review status | Current path | Updated |
+
+ {% for doc in recently_updated %}
+
+ | {{ doc.document_id }} |
+ {{ doc.document_type }} |
+ {{ doc.review_status }} |
+ {{ doc.current_path }} |
+ {{ doc.updated_at }} |
+
+ {% endfor %}
+
+
+
+ {% endif %}
+
+
+
+
+
+
+
diff --git a/app/templates/trash/index.html b/app/templates/trash/index.html
index 9bae0c2..57652d9 100644
--- a/app/templates/trash/index.html
+++ b/app/templates/trash/index.html
@@ -7,22 +7,7 @@
-
+ {% include "partials/sidebar.html" %}
diff --git a/app/templates/trash/index.html.bak_sidebar b/app/templates/trash/index.html.bak_sidebar
new file mode 100644
index 0000000..ae68aca
--- /dev/null
+++ b/app/templates/trash/index.html.bak_sidebar
@@ -0,0 +1,97 @@
+
+
+
+
+
Trash
+
+
+
+
+
+
+
+
+
+
Trash
+
Soft-deleted documents can be restored or removed permanently.
+
+
+
+
+ {% if documents %}
+
+
+
+
+ | Document |
+ Type |
+ Review status |
+ Trashed at |
+ Current path |
+ Actions |
+
+
+
+ {% for doc in documents %}
+
+ | {{ doc.document_id }} |
+ {{ doc.document_type }} |
+ {{ doc.review_status }} |
+ {{ doc.trashed_at }} |
+ {{ doc.current_path }} |
+
+
+
+
+
+ |
+
+ {% endfor %}
+
+
+
+ {% else %}
+
Trash is empty.
+ {% endif %}
+
+
+
+
+
+
+