@pagamio/frontend-commons-lib 0.8.338 → 0.8.340

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -71,6 +71,11 @@ export interface EcommerceCartItem {
71
71
  discountAmount: number;
72
72
  totalAmount: number;
73
73
  lineTotal: number;
74
+ /** Fulfillment store id assigned to this line. Set for retail/ecommerce
75
+ * carts that may span multiple stores; null for event carts. */
76
+ storeId?: string | null;
77
+ /** Store display name, when the API includes it for convenience. */
78
+ storeName?: string | null;
74
79
  metadata?: CartItemMetadata | null;
75
80
  }
76
81
  export interface BundleItem {
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+ export interface ReservedBreakdownItem {
3
+ id: string;
4
+ /** Short label rendered for this row, e.g. "Cart #1234 (active)" */
5
+ label: string;
6
+ /** Optional secondary line, e.g. "expires in 14m" or "Transfer → Store B" */
7
+ detail?: string;
8
+ quantity: number;
9
+ }
10
+ export interface ReservedBreakdownBucket {
11
+ /** Total quantity for this bucket. */
12
+ quantity: number;
13
+ /** Individual contributing records. May be empty even when quantity > 0
14
+ * if the source only exposes aggregate data. */
15
+ items: ReservedBreakdownItem[];
16
+ /** When true, the bucket's `quantity` is informational and is NOT part
17
+ * of the parent `Reserved` total (e.g. pending stock transfers). */
18
+ informational?: boolean;
19
+ }
20
+ export interface ReservedBreakdownData {
21
+ total: number;
22
+ orders: ReservedBreakdownBucket;
23
+ transfers: ReservedBreakdownBucket;
24
+ allocations: ReservedBreakdownBucket;
25
+ }
26
+ export interface ReservedBreakdownPopoverProps {
27
+ /** Quantity value to render as the clickable trigger. */
28
+ reservedQuantity: number;
29
+ /** Lazy-load: fetcher invoked when the popover opens for the first time. */
30
+ fetch: () => Promise<ReservedBreakdownData>;
31
+ /** Optional className for the trigger button. */
32
+ className?: string;
33
+ /** Render variant — default styles the trigger as a link-like underlined
34
+ * number, matching the dense layouts in inventory tables. */
35
+ variant?: 'link' | 'plain';
36
+ }
37
+ /**
38
+ * Clickable Reserved-quantity cell that opens a popover breaking down the
39
+ * value by source (Orders, Transfers, Allocations). Data-agnostic — the
40
+ * consumer provides a `fetch` callback that resolves the breakdown for the
41
+ * specific stock-item or station-allocation row.
42
+ *
43
+ * The trigger only renders an interactive control when `reservedQuantity > 0`.
44
+ * At zero, it renders the plain quantity text so the table cell stays
45
+ * visually consistent without offering an interaction with nothing to show.
46
+ */
47
+ export declare const ReservedBreakdownPopover: React.FC<ReservedBreakdownPopoverProps>;
48
+ export default ReservedBreakdownPopover;
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { cn } from '../../helpers/utils';
4
+ import { Popover, PopoverContent, PopoverTrigger } from './Popover';
5
+ const formatQty = (q) => (Number.isInteger(q) ? String(q) : q.toFixed(2));
6
+ /**
7
+ * Clickable Reserved-quantity cell that opens a popover breaking down the
8
+ * value by source (Orders, Transfers, Allocations). Data-agnostic — the
9
+ * consumer provides a `fetch` callback that resolves the breakdown for the
10
+ * specific stock-item or station-allocation row.
11
+ *
12
+ * The trigger only renders an interactive control when `reservedQuantity > 0`.
13
+ * At zero, it renders the plain quantity text so the table cell stays
14
+ * visually consistent without offering an interaction with nothing to show.
15
+ */
16
+ export const ReservedBreakdownPopover = ({ reservedQuantity, fetch, className, variant = 'link', }) => {
17
+ const [state, setState] = React.useState({
18
+ loading: false,
19
+ data: undefined,
20
+ error: undefined,
21
+ });
22
+ const hasFetchedRef = React.useRef(false);
23
+ if (reservedQuantity <= 0) {
24
+ return _jsx("span", { className: className, children: formatQty(reservedQuantity) });
25
+ }
26
+ const handleOpenChange = (open) => {
27
+ if (!open || hasFetchedRef.current)
28
+ return;
29
+ hasFetchedRef.current = true;
30
+ setState({ loading: true, data: undefined, error: undefined });
31
+ fetch()
32
+ .then((data) => setState({ loading: false, data, error: undefined }))
33
+ .catch((err) => {
34
+ const message = err instanceof Error ? err.message : 'Failed to load reserved breakdown';
35
+ setState({ loading: false, data: undefined, error: message });
36
+ hasFetchedRef.current = false;
37
+ });
38
+ };
39
+ const triggerClasses = variant === 'link'
40
+ ? 'underline decoration-dotted underline-offset-4 hover:decoration-solid focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded'
41
+ : 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded';
42
+ return (_jsxs(Popover, { onOpenChange: handleOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: cn(triggerClasses, className), "aria-label": `View reserved breakdown (${formatQty(reservedQuantity)})`, children: formatQty(reservedQuantity) }) }), _jsx(PopoverContent, { align: "end", className: "w-80", children: _jsx(BreakdownBody, { state: state, reservedQuantity: reservedQuantity }) })] }));
43
+ };
44
+ const BreakdownBody = ({ state, reservedQuantity }) => {
45
+ if (state.loading) {
46
+ return _jsx("div", { className: "text-sm text-muted-foreground", children: "Loading breakdown\u2026" });
47
+ }
48
+ if (state.error) {
49
+ return _jsx("div", { className: "text-sm text-destructive", children: state.error });
50
+ }
51
+ if (!state.data) {
52
+ return _jsx("div", { className: "text-sm text-muted-foreground", children: "No data available" });
53
+ }
54
+ const { orders, transfers, allocations, total } = state.data;
55
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-baseline justify-between", children: [_jsx("h4", { className: "text-sm font-semibold", children: "Reserved breakdown" }), _jsx("span", { className: "text-sm font-mono", children: formatQty(total) })] }), _jsx(BucketSection, { title: "Orders", bucket: orders }), _jsx(BucketSection, { title: "Transfers", bucket: transfers }), _jsx(BucketSection, { title: "Allocations", bucket: allocations }), Math.abs(total - reservedQuantity) > 0.001 ? (_jsxs("p", { className: "text-xs text-muted-foreground italic", children: ["Breakdown total (", formatQty(total), ") differs from the row value (", formatQty(reservedQuantity), "). Data may have changed since the row was last loaded."] })) : null] }));
56
+ };
57
+ const BucketSection = ({ title, bucket }) => {
58
+ const isInformational = bucket.informational === true;
59
+ return (_jsxs("section", { children: [_jsxs("div", { className: "flex items-baseline justify-between", children: [_jsxs("span", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [title, isInformational ? (_jsx("span", { className: "ml-1 normal-case font-normal italic text-muted-foreground/80", children: "(informational)" })) : null] }), _jsx("span", { className: "text-sm font-mono", children: formatQty(bucket.quantity) })] }), bucket.items.length > 0 ? (_jsx("ul", { className: "mt-1 space-y-0.5", children: bucket.items.map((item) => (_jsxs("li", { className: "flex items-baseline justify-between gap-2 text-xs", children: [_jsxs("span", { className: "truncate", children: [_jsx("span", { children: item.label }), item.detail ? _jsxs("span", { className: "text-muted-foreground", children: [" \u00B7 ", item.detail] }) : null] }), _jsx("span", { className: "font-mono", children: formatQty(item.quantity) })] }, item.id))) })) : null, isInformational && bucket.items.length === 0 && bucket.quantity > 0 ? (_jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Pending records exist but no detail is available." })) : null, bucket.quantity === 0 && bucket.items.length === 0 ? (_jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: "None" })) : null] }));
60
+ };
61
+ export default ReservedBreakdownPopover;
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ export type StockLabelKind = 'onHand' | 'reserved' | 'availableToSell';
3
+ export interface StockLabelProps {
4
+ kind: StockLabelKind;
5
+ className?: string;
6
+ }
7
+ /**
8
+ * Inventory column label paired with a help-icon tooltip. Single source of
9
+ * truth for the three stock-quantity labels (On Hand / Reserved / Available
10
+ * to Sell) and their help text. Update copy here, not at the call site.
11
+ */
12
+ export declare const StockLabel: React.FC<StockLabelProps>;
13
+ export default StockLabel;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { HiInformationCircle } from 'react-icons/hi';
3
+ import { cn } from '../../helpers/utils';
4
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './Tooltip';
5
+ const STOCK_LABEL_CONFIG = {
6
+ onHand: {
7
+ label: 'On Hand',
8
+ helpText: 'Total stock physically in this location',
9
+ },
10
+ reserved: {
11
+ label: 'Reserved',
12
+ helpText: 'Stock already committed to orders, transfers, or allocations',
13
+ },
14
+ availableToSell: {
15
+ label: 'Available to Sell',
16
+ helpText: 'Stock currently available for sale',
17
+ },
18
+ };
19
+ /**
20
+ * Inventory column label paired with a help-icon tooltip. Single source of
21
+ * truth for the three stock-quantity labels (On Hand / Reserved / Available
22
+ * to Sell) and their help text. Update copy here, not at the call site.
23
+ */
24
+ export const StockLabel = ({ kind, className }) => {
25
+ const { label, helpText } = STOCK_LABEL_CONFIG[kind];
26
+ return (_jsx(TooltipProvider, { delayDuration: 150, children: _jsxs("span", { className: cn('inline-flex items-center gap-1', className), children: [_jsx("span", { children: label }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": `${label} help`, className: "text-muted-foreground hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-full", children: _jsx(HiInformationCircle, { className: "h-4 w-4", "aria-hidden": "true" }) }) }), _jsx(TooltipContent, { className: "max-w-xs", children: helpText })] })] }) }));
27
+ };
28
+ export default StockLabel;
@@ -63,6 +63,10 @@ export type { ImageUploaderProps } from './ImageUploader';
63
63
  export { default as NotificationModal } from './NotificationModal';
64
64
  export type { NotificationModalProps } from './NotificationModal';
65
65
  export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './Tooltip';
66
+ export { StockLabel, default as StockLabelDefault } from './StockLabel';
67
+ export type { StockLabelKind, StockLabelProps } from './StockLabel';
68
+ export { ReservedBreakdownPopover, default as ReservedBreakdownPopoverDefault } from './ReservedBreakdownPopover';
69
+ export type { ReservedBreakdownData, ReservedBreakdownBucket, ReservedBreakdownItem, ReservedBreakdownPopoverProps, } from './ReservedBreakdownPopover';
66
70
  export { default as TimerBadge } from './TimerBadge';
67
71
  export type { TimerBadgeProps } from './TimerBadge';
68
72
  export { Badge, default as BadgeDefault } from './Badge';
@@ -62,6 +62,10 @@ export { default as ImageUploader } from './ImageUploader';
62
62
  export { default as NotificationModal } from './NotificationModal';
63
63
  // Tooltip - v2 shadcn-style tooltip with Radix
64
64
  export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './Tooltip';
65
+ // StockLabel - inventory column label + help-icon tooltip
66
+ export { StockLabel, default as StockLabelDefault } from './StockLabel';
67
+ // ReservedBreakdownPopover - click-to-view breakdown of reserved stock
68
+ export { ReservedBreakdownPopover, default as ReservedBreakdownPopoverDefault } from './ReservedBreakdownPopover';
65
69
  // TimerBadge - elapsed time with urgency coloring (green → amber → red)
66
70
  export { default as TimerBadge } from './TimerBadge';
67
71
  // Badge - styled pill/tag component
package/lib/styles.css CHANGED
@@ -3117,6 +3117,9 @@ video {
3117
3117
  .capitalize {
3118
3118
  text-transform: capitalize;
3119
3119
  }
3120
+ .normal-case {
3121
+ text-transform: none;
3122
+ }
3120
3123
  .italic {
3121
3124
  font-style: italic;
3122
3125
  }
@@ -3487,6 +3490,9 @@ video {
3487
3490
  .line-through {
3488
3491
  text-decoration-line: line-through;
3489
3492
  }
3493
+ .decoration-dotted {
3494
+ text-decoration-style: dotted;
3495
+ }
3490
3496
  .underline-offset-2 {
3491
3497
  text-underline-offset: 2px;
3492
3498
  }
@@ -4475,6 +4481,9 @@ video {
4475
4481
  .hover\:underline:hover {
4476
4482
  text-decoration-line: underline;
4477
4483
  }
4484
+ .hover\:decoration-solid:hover {
4485
+ text-decoration-style: solid;
4486
+ }
4478
4487
  .hover\:opacity-100:hover {
4479
4488
  opacity: 1;
4480
4489
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pagamio/frontend-commons-lib",
3
3
  "description": "Pagamio library for Frontend reusable components like the form engine and table container",
4
- "version": "0.8.338",
4
+ "version": "0.8.340",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false