Files
temetro/frontend/components/charts/area-gradient-defs.tsx
T
Khalid Abdi 321a6298a4 frontend: pharmacy inventory, lab add-result & analysis charts
- Pharmacy: the sidebar entry now expands into Pharmacy + a new Inventory page
  (searchable medication stock with derived in-stock/low/out availability,
  backed by /api/inventory). Fix the dispensing-queue "Expiring" badge so it
  only flags courses ending within the next 7 days, not ones already elapsed.
- Lab "Add analysis result": the patient picker no longer lists patients until
  you type and supports arrow-key + Enter selection; the test field offers a
  catalog of common analyses with an Advanced option (custom analysis + ref
  range); submitted results now show immediately in a Recent results feed.
- Analysis: add a Live panel (real-time line chart) and replace the flat trend
  sparklines with bklit-ui area + bar charts (installed from the @bklit shadcn
  registry; chart CSS variables wired into the theme for light and dark).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 19:22:07 +03:00

114 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
type FadeEdges,
fadeGradientStops,
resolveFadeSides,
viewportFadeGradientAttrs,
} from "./fade-edges";
interface AreaGradientDefsProps {
gradientId: string;
strokeGradientId: string;
edgeMaskId: string;
edgeGradientId: string;
fill: string;
fillOpacity: number;
gradientToOpacity: number;
/** 01: where the bottom stop sits (1 = full-height gradient). */
gradientSpan?: number;
resolvedStroke: string;
isPatternFill: boolean;
fadeEdges: FadeEdges;
innerWidth: number;
innerHeight: number;
}
export function AreaGradientDefs({
gradientId,
strokeGradientId,
edgeMaskId,
edgeGradientId,
fill,
fillOpacity,
gradientToOpacity,
gradientSpan = 1,
resolvedStroke,
isPatternFill,
fadeEdges,
innerWidth,
innerHeight,
}: AreaGradientDefsProps) {
const sides = resolveFadeSides(fadeEdges);
// Stroke gradient mirrors the area's edge fade so the line doesn't pop in
// past the faded fill. Skip emitting it when neither edge fades — the line
// can then paint a solid stroke instead of an unnecessary url(#...) ref.
const strokeStops = sides.any ? fadeGradientStops(sides) : null;
const showEdgeMask = sides.any && !isPatternFill;
const edgeStops = showEdgeMask ? fadeGradientStops(sides) : null;
const span = Math.min(1, Math.max(0.01, gradientSpan));
const midOffset = `${span * 100}%`;
return (
<defs>
{isPatternFill ? null : (
<linearGradient id={gradientId} x1="0%" x2="0%" y1="0%" y2="100%">
<stop
offset="0%"
style={{ stopColor: fill, stopOpacity: fillOpacity }}
/>
<stop
offset={midOffset}
style={{ stopColor: fill, stopOpacity: gradientToOpacity }}
/>
{span < 1 ? (
<stop
offset="100%"
style={{ stopColor: fill, stopOpacity: gradientToOpacity }}
/>
) : null}
</linearGradient>
)}
{strokeStops ? (
<linearGradient
id={strokeGradientId}
{...viewportFadeGradientAttrs(innerWidth)}
>
{strokeStops.map((stop) => (
<stop
key={stop.offset}
offset={stop.offset}
style={{ stopColor: resolvedStroke, stopOpacity: stop.opacity }}
/>
))}
</linearGradient>
) : null}
{edgeStops ? (
<>
<linearGradient
id={edgeGradientId}
{...viewportFadeGradientAttrs(innerWidth)}
>
{edgeStops.map((stop) => (
<stop
key={stop.offset}
offset={stop.offset}
style={{ stopColor: "white", stopOpacity: stop.opacity }}
/>
))}
</linearGradient>
<mask id={edgeMaskId}>
<rect
fill={`url(#${edgeGradientId})`}
height={innerHeight}
width={innerWidth}
x="0"
y="0"
/>
</mask>
</>
) : null}
</defs>
);
}