Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | 69x 69x 66x 75x 75x 4x | import { Link } from "@tanstack/react-router";
import type { KanbanLead } from "../../lib/api/pro/crm";
import { abbreviatePaise } from "../../lib/currency-utils";
import { cn } from "../../lib/utils";
import { Button } from "../ui/button";
import { StatusBadge } from "../ui/status-badge";
import { DENSE_BUTTON } from "./lead-controls";
import { formatDueLabel, overdueLeads } from "./lead-overdue";
import { LEAD_STATUS_OVERDUE } from "./lead-status";
import { scopeLabel } from "./lead-vocabulary";
/**
* Overdue follow-ups, surfaced above the board.
*
* `/` (Today) reads exactly this data, so the two surfaces have to agree on
* what "overdue" means — and they do, because both call `overdueLeads()` from
* `lead-overdue.ts`. That module is the only definition; see its header.
*
* THIS IS THE PAGE'S ONE INDEX-RED MARK. The Index Red Rule gives a page a
* single 3px inset bar for the thing it is currently about, and on the Leads
* board that is the work waiting on the pro — not a stage, not a selection.
* Every other band on the page (the five column heads, the table head) is
* `.band-quiet` with a `--border-strong` marker for exactly that reason.
*
* Red here is the OVERDUE status code's ink (`--error`), which is a different
* token from `--index` and a different job: the code says "past due", the bar
* says "this is the section you are reading". They are allowed to share a page
* because they never mean the same thing.
*/
type OverdueFollowUpsProps = {
leads: readonly KanbanLead[];
/** One reading of the clock for the whole page. */
now: number;
/** Opens the note dialog — logging the call is how a follow-up is cleared. */
onLogContact: (leadId: number) => void;
};
export function OverdueFollowUps({
leads,
now,
onLogContact,
}: OverdueFollowUpsProps) {
const overdue = overdueLeads(leads, now);
if (overdue.length === 0) return null;
return (
<section aria-labelledby="overdue-follow-ups">
{/* The band, with the page's one index-red mark. */}
<div className="flex items-center gap-2 border-b border-border-strong pb-1.5">
<span
aria-hidden="true"
className="h-[1.05em] w-0.75 shrink-0 rounded-[1px] bg-index"
/>
<h2
id="overdue-follow-ups"
className="flex-1 text-xs font-semibold uppercase tracking-wider text-foreground-default"
>
Overdue follow-ups
</h2>
<span className="text-xs font-semibold tabular-nums text-foreground-muted">
{overdue.length}
</span>
</div>
<div className="mt-2.5 overflow-hidden rounded-lg bg-background-elevated shadow-card">
<ul className="flex flex-col">
{overdue.map((lead) => {
const meta = [
lead.location,
scopeLabel(lead.projectType),
lead.quoteValuePaise != null
? `₹ ${abbreviatePaise(lead.quoteValuePaise)}`
: null,
]
.filter(Boolean)
.join(" · ");
return (
<li
key={lead.id}
className="flex items-start gap-3 border-t border-border-default px-3.5 py-2.5 transition-colors first:border-t-0 hover:bg-foreground-default/7"
>
<StatusBadge
status={LEAD_STATUS_OVERDUE.code}
variant={LEAD_STATUS_OVERDUE.tone}
className="mt-0.5"
/>
<span className="min-w-0 flex-1">
<Link
to="/leads/$leadId"
params={{ leadId: String(lead.id) }}
className="block truncate font-medium text-foreground-default underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
>
Call {lead.customerName}
</Link>
{meta && (
<span className="block truncate text-sm text-foreground-muted">
{meta}
</span>
)}
</span>
<span className="shrink-0 whitespace-nowrap text-xs font-semibold uppercase tracking-wider tabular-nums text-foreground-muted">
{/* Non-null: overdueLeads() filtered on it. */}
{formatDueLabel(lead.nextReminderDueAt as string, now)}
</span>
<Button
variant="outline"
size="sm"
className={cn("shrink-0", DENSE_BUTTON)}
onClick={() => onLogContact(lead.id)}
>
Log a call
</Button>
</li>
);
})}
</ul>
</div>
</section>
);
}
|