new updates

This commit is contained in:
2026-09-17 14:22:45 +05:30
committed by ImanThiyanga
parent e1f19fe413
commit 8c3d62223c
261 changed files with 102091 additions and 549 deletions
@@ -20,10 +20,19 @@ type Props = {
data: number[]
backgroundColor?: string | string[]
}>
/** Container height in px — defaults to the original 260 so existing callers are unaffected. */
height?: number
/** Fraction of each category's slot a bar occupies (Chart.js default 0.9) — raise for thicker bars. */
barPercentage?: number
/** Fraction of the axis each category slot occupies (Chart.js default 0.8) — raise to shrink gaps between categories. */
categoryPercentage?: number
}
export default function BarChart({ labels, datasets }: Props) {
const data = { labels, datasets }
export default function BarChart({ labels, datasets, height = 260, barPercentage, categoryPercentage }: Props) {
const data = {
labels,
datasets: datasets.map((d) => ({ ...d, barPercentage, categoryPercentage })),
}
const options = {
responsive: true,
@@ -33,7 +42,7 @@ export default function BarChart({ labels, datasets }: Props) {
}
return (
<div style={{ width: "100%", minHeight: 260 }}>
<div style={{ width: "100%", height }}>
<Bar data={data} options={options} />
</div>
)
@@ -0,0 +1,92 @@
"use client"
import * as React from "react"
import { Search, SlidersHorizontal, X } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
interface FilterBarProps {
search: string
onSearchChange: (value: string) => void
searchPlaceholder?: string
searchAriaLabel?: string
/** Rendered to the left of the search box — status tabs/pills, typically. */
children?: React.ReactNode
/** Content shown inside the "Advanced" popover. Omit to hide the Advanced button entirely. */
advanced?: React.ReactNode
/** Shows a dot on the Advanced button when any advanced filter is set. */
advancedActive?: boolean
/** Shown as a "Clear advanced filters" action inside the popover when provided. */
onClearAdvanced?: () => void
className?: string
}
/**
* Standard list-page filter bar: search box + optional left-side content (status
* tabs/pills) + an optional "Advanced" popover for filters that don't fit inline
* (customer, warehouse, department, date range, ...). One shape, reused across
* Sales, HRM, and Warehouses list pages instead of each rolling its own.
*/
export function FilterBar({
search, onSearchChange, searchPlaceholder = "Search…", searchAriaLabel,
children, advanced, advancedActive, onClearAdvanced, className,
}: FilterBarProps) {
return (
<div className={cn("flex flex-col gap-3 lg:flex-row lg:items-center", className)}>
{children}
<div className="flex flex-1 flex-col gap-3 lg:flex-row lg:items-center lg:justify-end">
<div className="relative w-full lg:max-w-sm">
<Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
<Input
value={search}
onChange={(e) => onSearchChange(e.target.value)}
placeholder={searchPlaceholder}
className="h-10 pl-9 pr-9"
aria-label={searchAriaLabel ?? searchPlaceholder}
/>
{search && (
<button
type="button"
onClick={() => onSearchChange("")}
aria-label="Clear search"
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<X className="size-4" />
</button>
)}
</div>
{advanced && (
<Popover>
<PopoverTrigger
render={
<Button variant="outline" size="sm" className="relative shrink-0">
<SlidersHorizontal className="size-4" />
Advanced
{advancedActive && (
<span className="absolute -right-1 -top-1 size-2 rounded-full bg-primary" aria-hidden="true" />
)}
</Button>
}
/>
<PopoverContent align="end" className="w-72 p-4">
<div className="flex flex-col gap-3">
{advanced}
{onClearAdvanced && (
<Button variant="ghost" size="sm" onClick={onClearAdvanced} className="self-start">
Clear advanced filters
</Button>
)}
</div>
</PopoverContent>
</Popover>
)}
</div>
</div>
)
}
@@ -0,0 +1,28 @@
import { type LucideIcon } from "lucide-react"
import { StatCard } from "@/components/ui/stat-card"
import { Skeleton } from "@/components/ui/skeleton"
export interface KpiTileSpec {
label: string
value: number
icon?: LucideIcon
}
/**
* A small row of KPI tiles for a list page — reuses the same GET /dashboard/stats
* aggregate the Dashboard home page renders (see hooks/use-dashboard-stats.ts), just
* picking out the one or two counts relevant to this page. Pass `null` while stats are
* still loading to render skeleton placeholders instead of a layout jump.
*/
export function KpiRow({ tiles }: { tiles: KpiTileSpec[] | null }) {
return (
<div className="mb-4 grid grid-cols-2 gap-3 sm:grid-cols-4">
{tiles
? tiles.map((tile) => (
<StatCard key={tile.label} label={tile.label} value={tile.value} icon={tile.icon} />
))
: Array.from({ length: 2 }).map((_, i) => <Skeleton key={i} className="h-[90px] rounded-xl" />)}
</div>
)
}