feat: tighten line item review queue layout and scale

This commit is contained in:
Sean McElwain 2026-04-28 23:18:22 -05:00
parent c19753ec04
commit f2f2ac3310
3 changed files with 375 additions and 57 deletions

View File

@ -5763,3 +5763,323 @@ table {
stroke-linecap: round; stroke-linecap: round;
stroke-linejoin: round; stroke-linejoin: round;
} }
/* ===== line item queue styled like document review ===== */
.queue-review-card {
padding: 1rem 1rem 0.95rem 1rem;
border-radius: 1.5rem;
box-shadow: 0 4px 18px rgba(15, 23, 42, 0.06);
}
.queue-review-card .topbar {
margin-bottom: 0.8rem !important;
align-items: flex-start;
gap: 0.75rem;
}
.queue-review-meta {
font-size: 0.94rem;
color: #6b7280;
line-height: 1.25;
margin-bottom: 0.12rem;
}
.queue-review-title {
margin: 0.1rem 0 0 0 !important;
font-size: 1.15rem;
line-height: 1.15;
font-weight: 700;
}
.queue-review-raw {
margin-top: 0.18rem;
font-size: 0.96rem;
color: #8a8f98;
line-height: 1.2;
}
.queue-review-badges {
gap: 0.45rem;
align-self: flex-start;
}
.queue-review-badges .badge {
font-size: 0.84rem;
padding: 0.32rem 0.7rem;
}
.queue-review-form-grid {
gap: 0.9rem;
}
.queue-review-form-grid .form-field label {
font-size: 0.92rem;
margin-bottom: 0.35rem;
color: #4b5563;
}
.queue-review-rating-input,
.queue-review-note-input {
border-radius: 1.1rem;
}
.queue-review-rating-input {
min-height: 3rem;
}
.queue-review-note-input {
min-height: 6.25rem;
}
.queue-review-checkbox-row {
gap: 1rem;
align-items: center;
flex-wrap: wrap;
}
.queue-review-checkbox-row label {
margin: 0;
font-size: 0.92rem;
}
.queue-review-actions {
gap: 0.6rem;
align-items: center;
flex-wrap: wrap;
}
.queue-review-actions .button-link,
.queue-review-actions button {
min-height: 2.65rem;
border-radius: 999px;
}
.tab-panel[data-panel="queue"] > .card-title {
margin-bottom: 0.75rem;
}
.tab-panel[data-panel="queue"] > .empty-state,
.tab-panel[data-panel="queue"] .empty-state {
margin-top: 0.25rem;
}
@media (max-width: 900px) {
.queue-review-card {
padding: 0.95rem 0.95rem 0.9rem 0.95rem;
}
.queue-review-card .topbar {
flex-direction: column;
align-items: flex-start;
gap: 0.55rem;
}
.queue-review-title {
font-size: 1.08rem;
}
.queue-review-badges {
width: 100%;
}
.queue-review-rating-input {
min-height: 2.8rem;
}
.queue-review-note-input {
min-height: 5.2rem;
}
.queue-review-actions {
gap: 0.5rem;
}
}
/* ===== end line item queue styled like document review ===== */
/* ===== line item queue header relayout ===== */
.queue-review-card {
padding: 0.82rem;
border-radius: 1.35rem;
}
.queue-review-header {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
gap: 0.58rem;
align-items: start;
margin-bottom: 0.58rem;
}
.queue-review-title-block {
min-width: 0;
}
.queue-review-meta {
font-size: 0.78rem;
color: #6b7280;
line-height: 1.08;
margin-bottom: 0.08rem;
}
.queue-review-title {
margin: 0 !important;
font-size: 0.88rem;
line-height: 1.02;
font-weight: 700;
}
.queue-review-raw {
margin-top: 0.12rem;
font-size: 0.78rem;
color: #8a8f98;
line-height: 1.08;
}
.queue-review-flags {
display: flex;
flex-direction: column;
gap: 0.18rem;
align-self: start;
padding-top: 0;
}
.queue-flag-row {
display: flex;
align-items: center;
gap: 0.28rem;
font-size: 0.76rem;
color: #4b5563;
white-space: nowrap;
margin: 0;
line-height: 1;
}
.queue-flag-row input[type="checkbox"] {
width: 0.92rem;
height: 0.92rem;
margin: 0;
}
.queue-review-badges {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 0.28rem;
align-self: start;
}
.queue-review-badges .badge {
white-space: nowrap;
font-size: 0.74rem;
padding: 0.2rem 0.5rem;
line-height: 1.05;
}
.queue-review-body {
display: grid;
gap: 0.52rem;
}
.queue-review-body .form-field label {
font-size: 0.76rem;
margin-bottom: 0.14rem;
color: #6b7280;
line-height: 1.05;
}
.queue-review-rating-input,
.queue-review-note-input {
border-radius: 0.92rem;
font-size: 0.82rem;
}
.queue-review-rating-input {
min-height: 2.35rem;
}
.queue-review-note-input {
min-height: 4.3rem;
}
.queue-review-actions {
margin-top: 0.62rem;
gap: 0.42rem;
flex-wrap: wrap;
}
.queue-review-actions .button-link,
.queue-review-actions button {
min-height: 2.18rem;
font-size: 0.82rem;
padding: 0.48rem 0.9rem;
}
@media (max-width: 900px) {
.queue-review-card {
padding: 0.78rem;
border-radius: 1.2rem;
}
.queue-review-header {
grid-template-columns: minmax(0, 1fr) auto auto;
gap: 0.46rem;
margin-bottom: 0.5rem;
}
.queue-review-meta {
font-size: 0.74rem;
}
.queue-review-title {
font-size: 0.84rem;
}
.queue-review-raw {
font-size: 0.74rem;
}
.queue-flag-row {
font-size: 0.72rem;
}
.queue-flag-row input[type="checkbox"] {
width: 0.86rem;
height: 0.86rem;
}
.queue-review-badges .badge {
font-size: 0.7rem;
padding: 0.18rem 0.45rem;
}
.queue-review-body .form-field label {
font-size: 0.72rem;
}
.queue-review-rating-input,
.queue-review-note-input {
font-size: 0.8rem;
}
.queue-review-rating-input {
min-height: 2.2rem;
}
.queue-review-note-input {
min-height: 3.9rem;
}
.queue-review-actions .button-link,
.queue-review-actions button {
min-height: 2.05rem;
font-size: 0.8rem;
padding: 0.44rem 0.82rem;
}
}
/* ===== end line item queue header relayout ===== */

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Document Processor{% endblock %}</title> <title>{% block title %}Document Processor{% endblock %}</title>
<link rel="stylesheet" href="/static/app.css?v=161"> <link rel="stylesheet" href="/static/app.css?v=166">
<link rel="stylesheet" href="/static/app-shell.css?v=158"> <link rel="stylesheet" href="/static/app-shell.css?v=158">
</head> </head>
<body> <body>

View File

@ -168,7 +168,7 @@
<input type="hidden" name="rating_max" value="{{ rating_max }}"> <input type="hidden" name="rating_max" value="{{ rating_max }}">
<input type="hidden" name="return_to" value="list"> <input type="hidden" name="return_to" value="list">
<div class="form-grid"> <div class="form-grid queue-review-form-grid">
<div class="form-field"> <div class="form-field">
<label for="quality_rating_{{ row.line_item_id }}">Quality rating</label> <label for="quality_rating_{{ row.line_item_id }}">Quality rating</label>
<input id="quality_rating_{{ row.line_item_id }}" type="text" name="quality_rating" value="{{ row.quality_rating }}" placeholder="e.g. 8.5 or 4/5"> <input id="quality_rating_{{ row.line_item_id }}" type="text" name="quality_rating" value="{{ row.quality_rating }}" placeholder="e.g. 8.5 or 4/5">
@ -206,68 +206,66 @@
{% if queue_rows %} {% if queue_rows %}
{% for row in queue_rows %} {% for row in queue_rows %}
<div class="card" style="margin-bottom: 1rem;"> <div class="card queue-review-card" id="queue-item-{{ row.line_item_id }}">
<div class="topbar" style="margin-bottom: 0.75rem;"> <form method="post" action="/line-items/{{ row.line_item_id }}/review">
<div> <input type="hidden" name="return_to" value="queue">
<div class="page-subtitle">{{ row.transaction_date }} · {{ row.merchant }}</div>
<h3 class="card-title" style="margin: 0.2rem 0 0 0;">{{ row.description }}</h3>
<div class="page-subtitle">{{ row.raw_description }}</div>
</div>
<div class="badges">
{% if row.category %}
<span class="badge">{{ row.category }}</span>
{% endif %}
{% if row.quantity %}
<span class="badge">Qty {{ row.quantity }}</span>
{% endif %}
{% if row.line_total %}
<span class="badge">${{ row.line_total }}</span>
{% endif %}
{% if row.confidence %}
<span class="badge">Conf {{ row.confidence }}</span>
{% endif %}
{% if row.quality_rating %}
<span class="badge reviewed">Rating {{ row.quality_rating }}</span>
{% endif %}
</div>
</div>
<form method="post" action="/line-items/{{ row.line_item_id }}/review"> <div class="queue-review-header">
<input type="hidden" name="q" value=""> <div class="queue-review-title-block">
<input type="hidden" name="merchant" value=""> <div class="queue-review-meta">{{ row.transaction_date }} · {{ row.merchant }}</div>
<input type="hidden" name="category" value=""> <h3 class="card-title queue-review-title">{{ row.description }}</h3>
<input type="hidden" name="date_from" value=""> {% if row.raw_description and row.raw_description != row.description %}
<input type="hidden" name="date_to" value=""> <div class="queue-review-raw">{{ row.raw_description }}</div>
<input type="hidden" name="rating_min" value=""> {% endif %}
<input type="hidden" name="rating_max" value=""> </div>
<input type="hidden" name="return_to" value="queue">
<div class="form-grid"> <div class="queue-review-flags">
<div class="form-field"> <label class="queue-flag-row">
<label for="queue_quality_rating_{{ row.line_item_id }}">Quality rating</label> <input type="checkbox" name="is_approved" value="1" {% if row.is_approved %}checked{% endif %}>
<input id="queue_quality_rating_{{ row.line_item_id }}" type="text" name="quality_rating" value="{{ row.quality_rating }}" placeholder="e.g. 8.5 or 4/5"> <span>Approved</span>
</div> </label>
<div class="form-field"> <label class="queue-flag-row">
<label>Review flags</label> <input type="checkbox" name="is_excluded" value="1" {% if row.is_excluded %}checked{% endif %}>
<div style="display:flex; gap:0.75rem; flex-wrap:wrap; margin-top:0.35rem;"> <span>Excluded</span>
<label><input type="checkbox" name="is_approved" value="1" {% if row.is_approved %}checked{% endif %}> Approved</label> </label>
<label><input type="checkbox" name="is_excluded" value="1" {% if row.is_excluded %}checked{% endif %}> Excluded</label> <label class="queue-flag-row">
<label><input type="checkbox" name="is_na" value="1" {% if row.is_na %}checked{% endif %} onchange="toggleReviewNA(this)"> N/A</label> <input type="checkbox" name="is_na" value="1" {% if row.is_na %}checked{% endif %}>
<span>N/A</span>
</label>
</div>
<div class="badges queue-review-badges">
{% if row.category %}
<span class="badge">{{ row.category }}</span>
{% endif %}
{% if row.quantity %}
<span class="badge">Qty {{ (row.quantity | string | replace(".0000", "") | replace(".00", "")) }}</span>
{% endif %}
{% if row.line_total %}
<span class="badge">${{ "%.2f"|format(row.line_total|float) }}</span>
{% endif %}
</div> </div>
</div> </div>
<div class="form-field full">
<label for="queue_quality_note_{{ row.line_item_id }}">Quality note</label> <div class="queue-review-body">
<textarea id="queue_quality_note_{{ row.line_item_id }}" name="quality_note" rows="3" placeholder="Taste, portion, texture, service notes...">{{ row.quality_note }}</textarea> <div class="form-field">
<label for="queue_quality_rating_{{ row.line_item_id }}">Quality rating</label>
<input id="queue_quality_rating_{{ row.line_item_id }}" class="queue-review-rating-input" type="text" name="quality_rating" value="{{ row.quality_rating }}" placeholder="e.g. 8.5 or 4/5">
</div>
<div class="form-field">
<label for="queue_quality_note_{{ row.line_item_id }}">Quality note</label>
<textarea id="queue_quality_note_{{ row.line_item_id }}" class="queue-review-note-input" name="quality_note" rows="3" placeholder="Taste, portion, texture, service notes...">{{ row.quality_note }}</textarea>
</div>
</div> </div>
</div>
<div class="button-row" style="margin-top: 1rem;"> <div class="button-row queue-review-actions">
<button class="primary" type="submit">Save rating/note</button> <button class="primary" type="submit">Save rating/note</button>
<a class="button-link" href="/documents/{{ row.document_id }}?tab=line-items">Open document</a> <a class="button-link" href="/documents/{{ row.document_id }}?tab=line-items">Open document</a>
</div> </div>
</form> </form>
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% else %}
<p class="empty-state">No line items are waiting for quality review.</p> <p class="empty-state">No line items are waiting for quality review.</p>