feat(inbox): show the hunt working, not just spinning (#1526)
A pass runs for over two minutes and reports nothing until it lands. The only sign of life was a spinner inside a button and a number that did not move, so the honest reading was that nothing was happening. A band under the header now names the mailboxes actually being read, keeps a running count of omgång and hämtade as passes land, and offers Avbryt without hunting for it in Settings. The bar is deliberately indeterminate. There is no honest percentage inside a pass, and inventing one to look finished is worse than admitting the wait. Reduced motion holds the sweep still and dims it instead of removing it, because the band still has to read as active. The result line waits for the run to end rather than sitting under a live one, so a press replaces itself in place. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -704,3 +704,23 @@ input[type="number"] {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Indeterminate sweep for the receipt hunt.
|
||||
A pass runs for over two minutes and reports nothing until it lands, so
|
||||
there is no honest percentage to show inside one. This says "still working"
|
||||
without inventing progress. Reduced motion holds it still and dims it
|
||||
rather than removing it: the band still has to read as active. */
|
||||
@keyframes hunt-sweep {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(400%); }
|
||||
}
|
||||
.hunt-sweep {
|
||||
animation: hunt-sweep 1.6s ease-in-out infinite;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hunt-sweep {
|
||||
animation: none;
|
||||
width: 100%;
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { cn, formatCurrency, formatDate, formatDateLong } from '@/lib/utils'
|
||||
import { QUIET_LINK_CLASS } from '@/components/ui/dry-table'
|
||||
import { GoogleMark, MicrosoftMark } from '@/components/ui/provider-marks'
|
||||
import EditKonteringDialog from '@/components/extensions/general/EditKonteringDialog'
|
||||
import { WhatsAppMark } from '@/components/extensions/general/WhatsAppMark'
|
||||
@@ -1136,9 +1137,7 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
{hunting ? (
|
||||
<>
|
||||
<Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />
|
||||
{huntProgress
|
||||
? `Letar… ${huntProgress.fetched} hittade`
|
||||
: 'Letar…'}
|
||||
{t('hunt_stop')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -1168,7 +1167,46 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{huntResult && (
|
||||
{/* A pass takes over two minutes and reports nothing until it lands, so
|
||||
a spinner alone leaves somebody watching a button. This says which
|
||||
mailboxes are being read, what has been found so far, and keeps
|
||||
moving while the pass is silent. The bar is deliberately
|
||||
indeterminate: there is no honest percentage inside a pass, and a
|
||||
fake one is worse than none. */}
|
||||
{hunting && (
|
||||
<div className="border-b bg-secondary/30">
|
||||
<div className="h-0.5 overflow-hidden bg-border/40">
|
||||
<div className="hunt-sweep h-full w-1/3 bg-foreground/40" />
|
||||
</div>
|
||||
<div className="px-4 py-2.5 flex items-center gap-3 flex-wrap text-xs">
|
||||
<span className="flex items-center gap-2 min-w-0">
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">
|
||||
{t('hunt_reading', {
|
||||
mailboxes: mailConnections
|
||||
.filter((c) => c.status === 'active')
|
||||
.map((c) => c.emailAddress)
|
||||
.join(', '),
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex-1" />
|
||||
{huntProgress && (
|
||||
<span className="text-muted-foreground tabular-nums">
|
||||
{t('hunt_progress', {
|
||||
pass: huntProgress.passes,
|
||||
found: huntProgress.fetched,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
<button type="button" onClick={stopHunt} className={QUIET_LINK_CLASS}>
|
||||
{t('hunt_stop')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hunting && huntResult && (
|
||||
<div className="border-b px-4 py-2 text-xs flex items-center gap-2">
|
||||
{huntResult.failed ? (
|
||||
<span className="text-warning">
|
||||
|
||||
@@ -3049,6 +3049,9 @@
|
||||
"mixed_currency_note": "Documents in different currencies cannot be summed into a single amount. Each document is still booked separately, against its matched bank transaction and the amount the bank actually settled in SEK."
|
||||
},
|
||||
"inbox_workspace": {
|
||||
"hunt_stop": "Stop",
|
||||
"hunt_reading": "Reading {mailboxes}",
|
||||
"hunt_progress": "pass {pass} · {found} fetched",
|
||||
"dialog_no_document": "No document",
|
||||
"sources_one": "{count} source",
|
||||
"sources_many": "{count} sources",
|
||||
|
||||
@@ -3049,6 +3049,9 @@
|
||||
"mixed_currency_note": "Underlag i olika valutor kan inte summeras till ett belopp. Varje underlag bokförs ändå var för sig, mot sin matchade banktransaktion och det belopp banken faktiskt drog i SEK."
|
||||
},
|
||||
"inbox_workspace": {
|
||||
"hunt_stop": "Avbryt",
|
||||
"hunt_reading": "Läser {mailboxes}",
|
||||
"hunt_progress": "omgång {pass} · {found} hämtade",
|
||||
"dialog_no_document": "Ingen handling",
|
||||
"sources_one": "{count} källa",
|
||||
"sources_many": "{count} källor",
|
||||
|
||||
Reference in New Issue
Block a user