Add backup images for login and background removal

- Added loginerps.png.bak (1134 KB) as a backup for the login image.
- Added whithoutbackground.png.bak (3455 KB) as a backup for the image without background.
This commit is contained in:
2026-09-15 15:24:48 +05:30
parent b3b6a25ce8
commit 149b87bfeb
77 changed files with 8935 additions and 1165 deletions
@@ -42,7 +42,6 @@ import {
PieChart,
Receipt,
ReceiptText,
Ruler,
Scale,
ScrollText,
Settings,
@@ -77,7 +76,15 @@ const navItems: {
expandOnly?: boolean
icon: LucideIcon
chevron?: boolean
children?: { title: string; code: string; href: string; icon: LucideIcon }[]
children?: {
title: string
code: string
href: string
icon: LucideIcon
/** Extra codes that also grant visibility for this row (e.g. a merged Brand/UOM
* tab page stays visible to a role that only has the UOM permission). */
altCodes?: string[]
}[]
}[] = [
{ title: "Dashboard", code: "dashboard", href: "/dashboard", icon: LayoutGrid },
{
@@ -89,8 +96,7 @@ const navItems: {
children: [
{ title: "Item", code: "products.item", href: "/dashboard/products", icon: Boxes },
{ title: "Category", code: "products.category", href: "/dashboard/products/categories", icon: ListTree },
{ title: "Brand", code: "products.brand", href: "/dashboard/products/brands", icon: Tag },
{ title: "UOM", code: "products.uom", href: "/dashboard/products/uoms", icon: Ruler },
{ title: "Brand/UOM", code: "products.brand", href: "/dashboard/products/brands", icon: Tag, altCodes: ["products.uom"] },
],
},
{
@@ -444,10 +450,17 @@ export function AppSidebar() {
const visibleItems = loading
? []
: navItems
.filter((item) => bypassCodes.has(item.code) || navCodes.includes(item.code) || item.children?.some((c) => navCodes.includes(c.code)))
.filter(
(item) =>
bypassCodes.has(item.code) ||
navCodes.includes(item.code) ||
item.children?.some((c) => navCodes.includes(c.code) || c.altCodes?.some((ac) => navCodes.includes(ac)))
)
.map((item) => ({
...item,
children: bypassCodes.has(item.code) ? item.children : item.children?.filter((c) => navCodes.includes(c.code)),
children: bypassCodes.has(item.code)
? item.children
: item.children?.filter((c) => navCodes.includes(c.code) || c.altCodes?.some((ac) => navCodes.includes(ac))),
}))
// Close mobile menu on route change
@@ -112,10 +112,9 @@ function titleFromPath(pathname: string) {
if (pathname === "/dashboard/products") return "Items"
if (pathname === "/dashboard/products/new") return "New Item"
if (pathname === "/dashboard/products/uoms") return "Units of Measure"
if (pathname === "/dashboard/products/categories") return "Categories"
if (pathname.startsWith("/dashboard/products/categories/")) return "Subcategories"
if (pathname === "/dashboard/products/brands") return "Brands"
if (pathname === "/dashboard/products/brands") return "Brand/UOM"
if (pathname === "/dashboard/products/item-types") return "Item Types"
if (pathname === "/dashboard/products/settings") return "Product Configuration"
if (pathname === "/dashboard/settings/notification-config") return "Notification Config"
@@ -48,7 +48,7 @@ export function ThemeToggle({ className }: { className?: string }) {
"flex size-8 shrink-0 items-center justify-center rounded-full transition-colors",
isActive
? isVibrant
? "bg-linear-to-br from-indigo-500 to-violet-600 text-white shadow-sm"
? "bg-linear-to-br from-sidebar-primary to-primary text-primary-foreground shadow-sm"
: "bg-card text-primary shadow-sm ring-1 ring-foreground/10"
: "text-muted-foreground hover:text-foreground"
)}
+2 -2
View File
@@ -8,9 +8,9 @@ const buttonVariants = cva(
{
variants: {
variant: {
default: "bg-black text-white hover:bg-black/90 active:bg-black",
default: "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary",
outline:
"border-black bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/20 dark:hover:bg-input/40",
"border-primary bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/20 dark:hover:bg-input/40",
secondary:
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_8%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost:
@@ -78,11 +78,11 @@ export function Combobox<Value>({
<ComboboxPrimitive.Item
key={String(item.value)}
value={item.value}
className="relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1.5 pr-8 pl-2.5 text-sm outline-hidden select-none data-highlighted:bg-violet-100 data-highlighted:text-violet-900 dark:data-highlighted:bg-violet-500/25 dark:data-highlighted:text-violet-200"
className="relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1.5 pr-8 pl-2.5 text-sm outline-hidden select-none data-highlighted:bg-secondary data-highlighted:text-secondary-foreground"
>
{item.label}
<ComboboxPrimitive.ItemIndicator className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
<CheckIcon className="size-4 text-violet-600 dark:text-violet-300" />
<CheckIcon className="size-4 text-primary" />
</ComboboxPrimitive.ItemIndicator>
</ComboboxPrimitive.Item>
)}
@@ -88,7 +88,7 @@ function DropdownMenuItem({
data-inset={inset}
data-variant={variant}
className={cn(
"group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
"group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-secondary focus:text-secondary-foreground not-data-[variant=destructive]:focus:**:text-secondary-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
className
)}
{...props}
@@ -113,7 +113,7 @@ function DropdownMenuSubTrigger({
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-secondary focus:text-secondary-foreground not-data-[variant=destructive]:focus:**:text-secondary-foreground data-inset:pl-7 data-popup-open:bg-secondary data-popup-open:text-secondary-foreground data-open:bg-secondary data-open:text-secondary-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
@@ -159,7 +159,7 @@ function DropdownMenuCheckboxItem({
data-slot="dropdown-menu-checkbox-item"
data-inset={inset}
className={cn(
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-secondary focus:text-secondary-foreground focus:**:text-secondary-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
@@ -201,7 +201,7 @@ function DropdownMenuRadioItem({
data-slot="dropdown-menu-radio-item"
data-inset={inset}
className={cn(
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-secondary focus:text-secondary-foreground focus:**:text-secondary-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
@@ -0,0 +1,89 @@
"use client"
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
import { PaginationMeta } from "@/types/common"
interface PaginationFooterProps {
pagination: PaginationMeta
onPageChange: (page: number) => void
}
/** First, last, current ±1, and ellipses for the gaps — keeps the row from overflowing
* on tables with many pages while still surfacing the ends. */
function pageItems(current: number, total: number): (number | "ellipsis")[] {
const items: (number | "ellipsis")[] = [1]
if (current - 1 > 2) items.push("ellipsis")
for (let p = Math.max(2, current - 1); p <= Math.min(total - 1, current + 1); p++) items.push(p)
if (current + 1 < total - 1) items.push("ellipsis")
if (total > 1) items.push(total)
return items
}
/** Server-paginated table footer: "X – Y of Z" plus numbered page links, built on the
* shadcn-style pagination primitives. Use for any list page driven by a `PaginationMeta`
* from the API (as opposed to `DataTable`'s own client-side pagination). */
export function PaginationFooter({ pagination, onPageChange }: PaginationFooterProps) {
const { page, pageSize, totalItems, totalPages } = pagination
if (totalPages <= 1) return null
const from = (page - 1) * pageSize + 1
const to = Math.min(page * pageSize, totalItems)
function go(e: React.MouseEvent, target: number) {
e.preventDefault()
if (target >= 1 && target <= totalPages) onPageChange(target)
}
return (
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-muted-foreground">
{from} – {to} of {totalItems}
</p>
<Pagination className="mx-0 w-auto justify-end">
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
text=""
onClick={(e) => go(e, page - 1)}
aria-disabled={page === 1}
className={page === 1 ? "pointer-events-none opacity-50" : undefined}
/>
</PaginationItem>
{pageItems(page, totalPages).map((item, i) =>
item === "ellipsis" ? (
<PaginationItem key={`ellipsis-${i}`}>
<PaginationEllipsis />
</PaginationItem>
) : (
<PaginationItem key={item}>
<PaginationLink href="#" isActive={item === page} onClick={(e) => go(e, item)}>
{item}
</PaginationLink>
</PaginationItem>
)
)}
<PaginationItem>
<PaginationNext
href="#"
text=""
onClick={(e) => go(e, page + 1)}
aria-disabled={page === totalPages}
className={page === totalPages ? "pointer-events-none opacity-50" : undefined}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
)
}
@@ -0,0 +1,36 @@
"use client"
import { Radio as RadioPrimitive } from "@base-ui/react/radio"
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group"
import { cn } from "@/lib/utils"
function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props<unknown>) {
return (
<RadioGroupPrimitive
data-slot="radio-group"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props<unknown>) {
return (
<RadioPrimitive.Root
data-slot="radio-group-item"
className={cn(
"relative flex size-4 shrink-0 items-center justify-center rounded-full border border-input transition-colors outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:bg-input/30 data-checked:border-primary",
className
)}
{...props}
>
<RadioPrimitive.Indicator
data-slot="radio-group-item-indicator"
className="flex items-center justify-center after:size-2 after:rounded-full after:bg-primary"
/>
</RadioPrimitive.Root>
)
}
export { RadioGroup, RadioGroupItem }
+2 -2
View File
@@ -150,7 +150,7 @@ function SelectItem({
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-violet-100 focus:text-violet-900 dark:focus:bg-violet-500/25 dark:focus:text-violet-200 not-data-[variant=destructive]:focus:**:text-violet-900 dark:not-data-[variant=destructive]:focus:**:text-violet-200 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
"relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-secondary focus:text-secondary-foreground not-data-[variant=destructive]:focus:**:text-secondary-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
@@ -163,7 +163,7 @@ function SelectItem({
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
}
>
<CheckIcon className="pointer-events-none text-violet-600 dark:text-violet-300" />
<CheckIcon className="pointer-events-none text-primary" />
</SelectPrimitive.ItemIndicator>
</SelectPrimitive.Item>
)
@@ -71,26 +71,26 @@ export function StatCard({
return (
<div
className={cn(
"flex flex-col gap-3 rounded-2xl bg-card p-5 shadow-sm ring-1 ring-foreground/10 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:ring-primary/30",
"flex flex-col gap-2 rounded-xl bg-card p-4 shadow-sm ring-1 ring-foreground/10 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:ring-primary/30",
className
)}
>
<div className="flex items-start justify-between gap-2">
<p className="text-base font-medium text-muted-foreground">{label}</p>
<p className="text-sm font-medium text-muted-foreground">{label}</p>
{Icon && (
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary/10">
<Icon className="size-5 text-primary" />
<div className="flex size-7 shrink-0 items-center justify-center rounded-lg bg-primary/10">
<Icon className="size-3.5 text-primary" />
</div>
)}
</div>
<div className="flex items-end justify-between gap-2">
<p className="text-xl font-bold tracking-tight text-foreground">{formatValue(value)}</p>
<p className="text-lg font-bold tracking-tight text-foreground">{formatValue(value)}</p>
{trend && trend.length > 1 && <Sparkline points={trend} />}
</div>
{hasDelta && (
<div className="flex items-center gap-1 text-sm">
<div className="flex items-center gap-1 text-xs">
<span
className={cn(
"font-semibold",