responsiveness and ui fixers

This commit is contained in:
2026-09-23 16:55:46 +05:30
parent 4c5e9b347a
commit f5e8e0e5b9
88 changed files with 9408 additions and 3576 deletions
@@ -3,7 +3,7 @@
import { useEffect, useRef, useState } from "react"
import Link from "next/link"
import { usePathname, useRouter } from "next/navigation"
import { ArrowLeft, Bell, Boxes, Building2, FileText, LogOut, Receipt, Search, Settings, User } from "lucide-react"
import { ArrowLeft, Bell, Boxes, Building2, FileText, LogOut, Receipt, Search, Settings, User, X } from "lucide-react"
import { authApi } from "@/lib/api/auth"
import { searchApi } from "@/lib/api/search"
@@ -201,7 +201,9 @@ export function Header() {
const [searchQuery, setSearchQuery] = useState("")
const [searchResults, setSearchResults] = useState<GlobalSearchResult[]>([])
const [searchOpen, setSearchOpen] = useState(false)
const [mobileSearchOpen, setMobileSearchOpen] = useState(false)
const searchBoxRef = useRef<HTMLDivElement>(null)
const mobileSearchBoxRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const term = searchQuery.trim()
@@ -221,6 +223,9 @@ export function Header() {
if (searchBoxRef.current && !searchBoxRef.current.contains(e.target as Node)) {
setSearchOpen(false)
}
if (mobileSearchBoxRef.current && !mobileSearchBoxRef.current.contains(e.target as Node)) {
setMobileSearchOpen(false)
}
}
document.addEventListener("mousedown", onClickOutside)
return () => document.removeEventListener("mousedown", onClickOutside)
@@ -228,6 +233,7 @@ export function Header() {
function goToSearchResult(result: GlobalSearchResult) {
setSearchOpen(false)
setMobileSearchOpen(false)
setSearchQuery("")
setSearchResults([])
router.push(SEARCH_TYPE_META[result.type].href(result.id))
@@ -263,7 +269,8 @@ export function Header() {
}
return (
<header className="print:hidden mx-3 mt-3 mb-3 flex items-center justify-between gap-2 rounded-3xl bg-card py-4 pr-3 pl-12 shadow-sm ring-1 ring-foreground/10 sm:mx-6 sm:pr-4 sm:pl-14 lg:mx-8 lg:mt-4 lg:mb-4 lg:gap-4 lg:p-4">
<header className="print:hidden mx-3 mt-3 mb-3 flex flex-col gap-2 rounded-3xl bg-card py-4 pr-3 pl-12 shadow-sm ring-1 ring-foreground/10 sm:mx-6 sm:pr-4 sm:pl-14 lg:mx-8 lg:mt-4 lg:mb-4 lg:p-4">
<div className="flex items-center justify-between gap-2 lg:gap-4">
<div className="flex min-w-0 flex-1 items-center gap-2 sm:gap-3">
{showBackButton && (
<button
@@ -279,6 +286,18 @@ export function Header() {
</div>
<div className="flex shrink-0 items-center gap-1.5 sm:gap-3">
<button
type="button"
onClick={() => setMobileSearchOpen((v) => !v)}
aria-label="Search"
className={cn(
"flex size-10 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground sm:hidden",
mobileSearchOpen && "bg-muted text-foreground"
)}
>
<Search className="size-5" />
</button>
{branchingEnabled && (isAdmin ? (
<Select value={activeBranchId ? String(activeBranchId) : ALL_BRANCHES} onValueChange={handleBranchChange}>
<SelectTrigger
@@ -375,10 +394,7 @@ export function Header() {
<Badge className="absolute top-1.5 right-1.5 size-2 rounded-full bg-primary p-0" />
)}
</PopoverTrigger>
<PopoverContent
align="end"
className="w-[calc(100vw-2rem)] max-w-sm sm:w-96"
>
<PopoverContent align="end" sideOffset={10} className="w-[calc(100vw-1.5rem)] max-w-[22rem] sm:w-96">
<div className="flex items-center justify-between border-b border-foreground/10 px-4 py-3">
<p className="text-sm font-semibold text-foreground">Notifications</p>
{unreadCount > 0 && (
@@ -477,6 +493,68 @@ export function Header() {
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{mobileSearchOpen && (
<div ref={mobileSearchBoxRef} className="relative sm:hidden">
<Search className="pointer-events-none absolute top-1/2 left-3 size-3.5 -translate-y-1/2 text-muted-foreground" />
<input
type="text"
autoFocus
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search item, PO, invoice, employee…"
className="h-10 w-full rounded-full border border-transparent bg-muted pr-9 pl-9 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50"
/>
<button
type="button"
onClick={() => {
setMobileSearchOpen(false)
setSearchQuery("")
setSearchResults([])
}}
aria-label="Close search"
className="absolute top-1/2 right-2 flex size-6 -translate-y-1/2 items-center justify-center rounded-full text-muted-foreground hover:bg-background hover:text-foreground"
>
<X className="size-4" />
</button>
{searchQuery.trim().length >= 2 && (
<div className="absolute top-full right-0 left-0 z-50 mt-2 max-h-96 overflow-y-auto rounded-xl bg-popover py-1.5 text-sm shadow-md ring-1 ring-foreground/10">
{searchResults.length === 0 ? (
<p className="p-4 text-center text-sm text-muted-foreground">No matches found.</p>
) : (
searchGrouped.map((group) => {
const meta = SEARCH_TYPE_META[group.type]
const Icon = meta.icon
return (
<div key={group.type}>
<p className="px-4 pt-1 pb-1 text-xs font-medium text-muted-foreground">{meta.label}</p>
{group.items.map((item) => (
<button
key={`${item.type}-${item.id}`}
type="button"
onClick={() => goToSearchResult(item)}
className="flex w-full items-center gap-3 px-4 py-2 text-left hover:bg-primary/10"
>
<Icon className="size-4 shrink-0 text-muted-foreground" />
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-foreground">{item.title}</p>
<p className="truncate text-xs text-muted-foreground">
{item.number}
{item.subtitle ? ` · ${item.subtitle}` : ""}
</p>
</div>
</button>
))}
</div>
)
})
)}
</div>
)}
</div>
)}
</header>
)
}