feat: add dashboard overview stats endpoint and UI integration
- Implemented `GET /dashboard/stats` in `DashboardController` to provide aggregate counts for stock, GRN, and procurement. - Created `DashboardStatsDto` and `WarehouseValuationDto` to structure the response data. - Developed `DashboardService` to fetch and compute necessary statistics from the database. - Added `IDashboardService` interface for service abstraction. - Introduced API client methods in `dashboard.ts` for frontend consumption of the new endpoint. - Defined TypeScript types for dashboard data in `dashboard.ts` to ensure type safety in the frontend. - Updated UI components in the frontend to reflect changes in the dashboard, including styling adjustments and removal of unused icons.
This commit is contained in:
@@ -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-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-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",
|
||||
"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",
|
||||
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" />
|
||||
<CheckIcon className="pointer-events-none text-violet-600 dark:text-violet-300" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
|
||||
@@ -36,8 +36,8 @@ function Sparkline({ points }: { points: number[] }) {
|
||||
className="h-4.5 w-12 shrink-0 overflow-visible"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d={path} fill="none" stroke="#cbd5e1" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
||||
<circle cx={lastX} cy={lastY} r={2.5} className="fill-indigo-600" stroke="white" strokeWidth={1.5} />
|
||||
<path d={path} fill="none" className="stroke-muted-foreground/40" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
||||
<circle cx={lastX} cy={lastY} r={2.5} className="fill-primary stroke-card" strokeWidth={1.5} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -71,21 +71,21 @@ export function StatCard({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-3 rounded-2xl bg-white p-5 shadow-sm ring-1 ring-black/5 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:ring-indigo-200",
|
||||
"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",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="text-base font-medium text-muted-foreground">{label}</p>
|
||||
{Icon && (
|
||||
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-indigo-50">
|
||||
<Icon className="size-5 text-indigo-600" />
|
||||
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary/10">
|
||||
<Icon className="size-5 text-primary" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-end justify-between gap-2">
|
||||
<p className="text-xl font-bold tracking-tight text-slate-900">{formatValue(value)}</p>
|
||||
<p className="text-xl font-bold tracking-tight text-foreground">{formatValue(value)}</p>
|
||||
{trend && trend.length > 1 && <Sparkline points={trend} />}
|
||||
</div>
|
||||
|
||||
@@ -94,7 +94,7 @@ export function StatCard({
|
||||
<span
|
||||
className={cn(
|
||||
"font-semibold",
|
||||
isGood ? "text-[#0ca30c]" : "text-[#d03b3b]"
|
||||
isGood ? "text-success" : "text-destructive"
|
||||
)}
|
||||
>
|
||||
{isPositive ? "+" : "-"}
|
||||
|
||||
@@ -23,7 +23,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
||||
return (
|
||||
<thead
|
||||
data-slot="table-header"
|
||||
className={cn("bg-primary/5 [&_tr]:border-b [&_tr]:hover:bg-primary/5", className)}
|
||||
className={cn("bg-muted/50 [&_tr]:border-b [&_tr]:hover:bg-muted/50", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -70,7 +70,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
||||
<th
|
||||
data-slot="table-head"
|
||||
className={cn(
|
||||
"h-10 px-2 text-left align-middle font-semibold whitespace-nowrap text-primary [&:has([role=checkbox])]:pr-0",
|
||||
"h-10 px-2 text-left align-middle font-semibold whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
Reference in New Issue
Block a user