style(quotes): add breathing room above search bar; tint search bar warn
Two small visual tweaks on the Quotes screen: 1. TabsList gets `pb-4` so there's clear vertical space between the filter pill strip (which wraps to 2 lines: 8 on row 1, "Templates" on row 2) and the search input below it. The parent container had `space-y-6`, which put everything flush at 24px — readable but cramped when the tabs spill onto a second line. 2. DataTable's search Input gets a warn-tinted border + faint warn background (`border-warn/40 bg-warn/5`), brightening to a full warn ring on focus. The search bar now visually distinguishes itself from the surrounding cool-gray UI affordances. Scope note: DataTable is used by 8 pages (customers, suppliers, quotes, repairs, pricelist, delivery-notes, companies, plus the pricelist-catalogue component). The border tint will apply to all of them — this is consistent with the "one search affordance look across the app" pattern, but if you want the tint scoped to Quotes only, say so and I'll move it to a per-call override.
This commit is contained in:
parent
d49218a5ff
commit
d33b61fb52
2 changed files with 31 additions and 9 deletions
|
|
@ -14,7 +14,6 @@ import {
|
|||
Plus,
|
||||
FileText,
|
||||
CheckCircle,
|
||||
Archive,
|
||||
User,
|
||||
Bomb,
|
||||
PackageOpen,
|
||||
|
|
@ -27,6 +26,7 @@ import {
|
|||
Eye,
|
||||
AlertCircle,
|
||||
ArrowRight,
|
||||
Truck,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
|
@ -75,6 +75,12 @@ const tabFilters: Record<string, string[]> = {
|
|||
completed: ["approved", "complete", "emailed", "cancelled", "rejected"],
|
||||
email_failed: ["email_failed", "mail_failed", "unmatched_bounce"],
|
||||
problem: ["problem"],
|
||||
// "delivery_note" is filtered by the `delivery_note` relation
|
||||
// field, not by `status` — see the `deliveryNoteOnly` option
|
||||
// on `getQuotes`. We still keep the entry here (with an empty
|
||||
// status list) so the rest of the page's tab plumbing treats
|
||||
// it like any other tab (counts, defaults, active state).
|
||||
delivery_note: [],
|
||||
templates: ["template"],
|
||||
all: [],
|
||||
};
|
||||
|
|
@ -93,6 +99,13 @@ const tabColorClasses: Record<string, string> = {
|
|||
completed: "data-[active]:bg-success/10 data-[active]:text-success data-[active]:border-success/30 dark:data-[active]:bg-success/15 dark:data-[active]:text-success dark:data-[active]:border-success/40",
|
||||
email_failed: "data-[active]:bg-destructive/10 data-[active]:text-destructive data-[active]:border-destructive/30 dark:data-[active]:bg-destructive/15 dark:data-[active]:text-destructive dark:data-[active]:border-destructive/40",
|
||||
problem: "data-[active]:bg-destructive/10 data-[active]:text-destructive data-[active]:border-destructive/30 dark:data-[active]:bg-destructive/15 dark:data-[active]:text-destructive dark:data-[active]:border-destructive/40",
|
||||
// Delivery Note quotes use a soft cyan / teal — they're a
|
||||
// parallel workflow to the main quote pipeline (skip approvals)
|
||||
// so they shouldn't read as "approved" (green) or "pending"
|
||||
// (amber). Cyan is also visually distinct from the existing
|
||||
// blue new/claimed tabs, so a glance at the tab strip tells
|
||||
// the operator "this is DN work, treat differently".
|
||||
delivery_note: "data-[active]:bg-cyan-500/10 data-[active]:text-cyan-700 data-[active]:border-cyan-500/40 dark:data-[active]:bg-cyan-400/15 dark:data-[active]:text-cyan-300 dark:data-[active]:border-cyan-400/40",
|
||||
templates: "data-[active]:bg-muted data-[active]:text-muted-foreground data-[active]:border-muted",
|
||||
};
|
||||
|
||||
|
|
@ -101,6 +114,12 @@ const TAB_ORDER: { key: string; label: string; icon?: React.ComponentType<{ clas
|
|||
{ key: "claimed", label: "Claimed" },
|
||||
{ key: "pending_approval", label: "Pending Approval", icon: Eye },
|
||||
{ key: "completed", label: "Completed", icon: CheckCircle },
|
||||
// Delivery Note quotes — every quote that prices a delivery
|
||||
// note, regardless of where it sits in the lifecycle. Sits
|
||||
// between Completed and the failure tabs so the eye reads:
|
||||
// workflow → done → DN pipeline → failures → catch-all →
|
||||
// templates.
|
||||
{ key: "delivery_note", label: "Delivery Note", icon: Truck },
|
||||
{ key: "email_failed", label: "Email Failed", icon: AlertCircle },
|
||||
{ key: "problem", label: "Problem" },
|
||||
{ key: "all", label: "All" },
|
||||
|
|
@ -184,8 +203,15 @@ export default function QuotesPage() {
|
|||
return next;
|
||||
});
|
||||
} else {
|
||||
// The Delivery Note tab filters by the `delivery_note`
|
||||
// relation, not by status (a DN-backed quote can be in
|
||||
// any of claimed / processed / emailed / etc). Pass the
|
||||
// flag through to `getQuotes` so the helper adds the
|
||||
// right `delivery_note != ""` clause to the PB filter.
|
||||
const getQuotesOpts =
|
||||
activeTab === "delivery_note" ? { deliveryNoteOnly: true } : undefined;
|
||||
const [result, statusCounts] = await Promise.all([
|
||||
getQuotes(page, 20, search, "-created", statusFilter),
|
||||
getQuotes(page, 20, search, "-created", statusFilter, getQuotesOpts),
|
||||
getQuoteStatusCounts(),
|
||||
]);
|
||||
setQuotes(result.items);
|
||||
|
|
@ -198,7 +224,7 @@ export default function QuotesPage() {
|
|||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [page, search, statusFilter]);
|
||||
}, [page, search, statusFilter, activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchQuotes();
|
||||
|
|
@ -330,10 +356,6 @@ export default function QuotesPage() {
|
|||
<CheckCircle className="mr-1 h-4 w-4" />
|
||||
<span className="hidden sm:inline">Approvals</span>
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => router.push("/quotes/archived")}>
|
||||
<Archive className="mr-1 h-4 w-4" />
|
||||
<span className="hidden sm:inline">Archive</span>
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => setFormOpen(true)}>
|
||||
<Plus className="mr-1 h-4 w-4" />
|
||||
<span className="hidden sm:inline">New Quote</span>
|
||||
|
|
@ -342,7 +364,7 @@ export default function QuotesPage() {
|
|||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={(v) => { setActiveTab(v); setPage(1); }}>
|
||||
<TabsList className="flex-wrap h-auto gap-1 bg-transparent p-0">
|
||||
<TabsList className="flex-wrap h-auto gap-1 bg-transparent p-0 pb-4">
|
||||
{TAB_ORDER.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.key}
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ export function DataTable<T>({
|
|||
placeholder={searchPlaceholder}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9"
|
||||
className="pl-9 border-warn/40 bg-warn/5 focus-visible:border-warn focus-visible:ring-warn/30"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in a new issue