feat: add fuzzy autocomplete and NL quick-add for invoice line items

- Description field now shows a fuzzy-matched dropdown of past line items
  (description, hours, rate) as you type via Fuse.js — zero server cost
- Selecting a suggestion pre-fills description, hours, and rate in one click
- NL quick-add bar lets you type e.g. "3hrs web design @120" + Enter to
  append a fully-parsed line item without clicking through fields
- New tRPC query `getLineItemHistory` returns deduplicated past line items
  for the current user, ordered by recency
- New `parseLineItem` utility handles hours/rate extraction via regex

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 03:08:00 -04:00
co-authored by Claude Sonnet 4.6
parent 0ba9c2f029
commit 7819e438df
7 changed files with 286 additions and 58 deletions
+30
View File
@@ -0,0 +1,30 @@
export interface ParsedLineItem {
description: string;
hours: number | null;
rate: number | null;
}
export function parseLineItem(input: string): ParsedLineItem {
let text = input.trim();
let hours: number | null = null;
let rate: number | null = null;
// Extract hours: "3h", "3hr", "3hrs", "3 hours", "3.5hours"
const hoursMatch = /(\d+\.?\d*)\s*h(?:ours?|rs?)\b/i.exec(text);
if (hoursMatch?.[0] && hoursMatch[1]) {
hours = parseFloat(hoursMatch[1]);
text = text.replace(hoursMatch[0], " ").trim();
}
// Extract rate: "@120", "@$120", "at 120", "at $120", "$120/hr", "$120ph"
const rateMatch = /(?:@\s*\$?|at\s+\$?)(\d+\.?\d*)|(\$\d+\.?\d*)(?:\/h(?:rs?)?|ph)?\b/i.exec(text);
if (rateMatch?.[0]) {
const rawRate = rateMatch[1] ?? rateMatch[2] ?? "";
rate = parseFloat(rawRate.replace("$", ""));
text = text.replace(rateMatch[0], " ").trim();
}
const description = text.replace(/\s+/g, " ").replace(/^[\s,]+|[\s,]+$/g, "").trim();
return { description: description || input.trim(), hours, rate };
}