@open-mercato/core 0.6.3-develop.3635.1.e4a05c16e8 → 0.6.3-develop.3653.1.b8558737ae
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/customer_settings/index.js +2 -0
- package/dist/generated/entities/customer_settings/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +1 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/customers/api/deals/aggregate/route.js +332 -0
- package/dist/modules/customers/api/deals/aggregate/route.js.map +7 -0
- package/dist/modules/customers/api/deals/bulk-update-owner/route.js +188 -0
- package/dist/modules/customers/api/deals/bulk-update-owner/route.js.map +7 -0
- package/dist/modules/customers/api/deals/bulk-update-stage/route.js +188 -0
- package/dist/modules/customers/api/deals/bulk-update-stage/route.js.map +7 -0
- package/dist/modules/customers/api/deals/route.js +255 -122
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/settings/stuck-threshold/route.js +169 -0
- package/dist/modules/customers/api/settings/stuck-threshold/route.js.map +7 -0
- package/dist/modules/customers/backend/config/customers/deals/page.js +181 -0
- package/dist/modules/customers/backend/config/customers/deals/page.js.map +7 -0
- package/dist/modules/customers/backend/config/customers/deals/page.meta.js +38 -0
- package/dist/modules/customers/backend/config/customers/deals/page.meta.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/create/page.js +17 -6
- package/dist/modules/customers/backend/customers/deals/create/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +2 -0
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ActivityComposerDialog.js +217 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ActivityComposerDialog.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/AddStageDialog.js +335 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/AddStageDialog.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/AddStageLane.js +29 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/AddStageLane.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/BulkActionsBar.js +160 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/BulkActionsBar.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js +157 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeStageDialog.js +114 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeStageDialog.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChipButton.js +36 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChipButton.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/CloseDateFilterPopover.js +199 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/CloseDateFilterPopover.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/CurrencyBreakdownTable.js +71 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/CurrencyBreakdownTable.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/CurrencyFilterPopover.js +233 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/CurrencyFilterPopover.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/CustomizeViewDialog.js +126 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/CustomizeViewDialog.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/DashedTileButton.js +31 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/DashedTileButton.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/DealCard.js +389 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/DealCard.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/DealCardMenu.js +181 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/DealCardMenu.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/EntityFilterPopover.js +199 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/EntityFilterPopover.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/FilterBarRow.js +44 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/FilterBarRow.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/FilterPopoverShell.js +70 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/FilterPopoverShell.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js +278 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/LaneCurrencyBreakdown.js +135 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/LaneCurrencyBreakdown.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/LaneResizeHandle.js +83 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/LaneResizeHandle.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/PipelineFilterPopover.js +155 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/PipelineFilterPopover.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js +318 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/SortByPopover.js +138 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/SortByPopover.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js +126 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +71 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/constants.js +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/constants.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +2330 -415
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +3 -3
- package/dist/modules/customers/commands/deals.js +6 -0
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +48 -13
- package/dist/modules/customers/commands/pipeline-stages.js.map +3 -3
- package/dist/modules/customers/commands/settings.js +45 -5
- package/dist/modules/customers/commands/settings.js.map +2 -2
- package/dist/modules/customers/data/enrichers.js +130 -1
- package/dist/modules/customers/data/enrichers.js.map +2 -2
- package/dist/modules/customers/data/entities.js +4 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +29 -2
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/lib/bulkDeals.js +150 -0
- package/dist/modules/customers/lib/bulkDeals.js.map +7 -0
- package/dist/modules/customers/lib/kysely.js +12 -0
- package/dist/modules/customers/lib/kysely.js.map +7 -0
- package/dist/modules/customers/lib/stuckDeals.js +32 -0
- package/dist/modules/customers/lib/stuckDeals.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260513203311_customers.js +13 -0
- package/dist/modules/customers/migrations/Migration20260513203311_customers.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.js +52 -0
- package/dist/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.js.map +7 -0
- package/dist/modules/customers/workers/deals-bulk-update-owner.js +41 -0
- package/dist/modules/customers/workers/deals-bulk-update-owner.js.map +7 -0
- package/dist/modules/customers/workers/deals-bulk-update-stage.js +41 -0
- package/dist/modules/customers/workers/deals-bulk-update-stage.js.map +7 -0
- package/generated/entities/customer_settings/index.ts +1 -0
- package/generated/entity-fields-registry.ts +1 -0
- package/package.json +7 -7
- package/src/modules/customers/api/deals/aggregate/route.ts +438 -0
- package/src/modules/customers/api/deals/bulk-update-owner/route.ts +216 -0
- package/src/modules/customers/api/deals/bulk-update-stage/route.ts +228 -0
- package/src/modules/customers/api/deals/route.ts +330 -135
- package/src/modules/customers/api/settings/stuck-threshold/route.ts +195 -0
- package/src/modules/customers/backend/config/customers/deals/page.meta.ts +36 -0
- package/src/modules/customers/backend/config/customers/deals/page.tsx +205 -0
- package/src/modules/customers/backend/customers/deals/create/page.tsx +25 -6
- package/src/modules/customers/backend/customers/deals/page.tsx +2 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/ActivityComposerDialog.tsx +277 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/AddStageDialog.tsx +425 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/AddStageLane.tsx +31 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/BulkActionsBar.tsx +171 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.tsx +196 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeStageDialog.tsx +138 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/ChipButton.tsx +47 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/CloseDateFilterPopover.tsx +232 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/CurrencyBreakdownTable.tsx +110 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/CurrencyFilterPopover.tsx +310 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/CustomizeViewDialog.tsx +169 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/DashedTileButton.tsx +54 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/DealCard.tsx +562 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/DealCardMenu.tsx +214 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/EntityFilterPopover.tsx +257 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/FilterBarRow.tsx +62 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/FilterPopoverShell.tsx +101 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/Lane.tsx +378 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/LaneCurrencyBreakdown.tsx +192 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/LaneResizeHandle.tsx +107 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/PipelineFilterPopover.tsx +196 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.tsx +408 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/SortByPopover.tsx +164 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.tsx +171 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +84 -0
- package/src/modules/customers/backend/customers/deals/pipeline/components/constants.ts +16 -0
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +2841 -550
- package/src/modules/customers/commands/deals.ts +19 -0
- package/src/modules/customers/commands/pipeline-stages.ts +68 -13
- package/src/modules/customers/commands/settings.ts +64 -5
- package/src/modules/customers/data/enrichers.ts +209 -2
- package/src/modules/customers/data/entities.ts +4 -1
- package/src/modules/customers/data/validators.ts +36 -2
- package/src/modules/customers/i18n/de.json +244 -0
- package/src/modules/customers/i18n/en.json +244 -0
- package/src/modules/customers/i18n/es.json +244 -0
- package/src/modules/customers/i18n/pl.json +244 -0
- package/src/modules/customers/lib/bulkDeals.ts +231 -0
- package/src/modules/customers/lib/kysely.ts +60 -0
- package/src/modules/customers/lib/stuckDeals.ts +88 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +16 -0
- package/src/modules/customers/migrations/Migration20260513203311_customers.ts +13 -0
- package/src/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.ts +71 -0
- package/src/modules/customers/workers/deals-bulk-update-owner.ts +46 -0
- package/src/modules/customers/workers/deals-bulk-update-stage.ts +46 -0
package/dist/modules/customers/backend/customers/deals/pipeline/components/CloseDateFilterPopover.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import {
|
|
5
|
+
Popover,
|
|
6
|
+
PopoverContent,
|
|
7
|
+
PopoverTrigger
|
|
8
|
+
} from "@open-mercato/ui/primitives/popover";
|
|
9
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
10
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
11
|
+
import { translateWithFallback } from "@open-mercato/shared/lib/i18n/translate";
|
|
12
|
+
import { ChipButton } from "./ChipButton.js";
|
|
13
|
+
import { FilterPopoverShell } from "./FilterPopoverShell.js";
|
|
14
|
+
function ymd(date) {
|
|
15
|
+
return date.toISOString().slice(0, 10);
|
|
16
|
+
}
|
|
17
|
+
function startOfMonth() {
|
|
18
|
+
const d = /* @__PURE__ */ new Date();
|
|
19
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
20
|
+
d.setUTCDate(1);
|
|
21
|
+
return d;
|
|
22
|
+
}
|
|
23
|
+
function endOfMonth() {
|
|
24
|
+
const d = startOfMonth();
|
|
25
|
+
d.setUTCMonth(d.getUTCMonth() + 1);
|
|
26
|
+
d.setUTCDate(0);
|
|
27
|
+
return d;
|
|
28
|
+
}
|
|
29
|
+
function startOfQuarter() {
|
|
30
|
+
const d = /* @__PURE__ */ new Date();
|
|
31
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
32
|
+
d.setUTCDate(1);
|
|
33
|
+
const m = d.getUTCMonth();
|
|
34
|
+
d.setUTCMonth(m - m % 3);
|
|
35
|
+
return d;
|
|
36
|
+
}
|
|
37
|
+
function endOfQuarter() {
|
|
38
|
+
const d = startOfQuarter();
|
|
39
|
+
d.setUTCMonth(d.getUTCMonth() + 3);
|
|
40
|
+
d.setUTCDate(0);
|
|
41
|
+
return d;
|
|
42
|
+
}
|
|
43
|
+
function buildPresetRange(key) {
|
|
44
|
+
const now = /* @__PURE__ */ new Date();
|
|
45
|
+
now.setUTCHours(0, 0, 0, 0);
|
|
46
|
+
switch (key) {
|
|
47
|
+
case "next-30": {
|
|
48
|
+
const to = new Date(now);
|
|
49
|
+
to.setUTCDate(to.getUTCDate() + 30);
|
|
50
|
+
return { from: ymd(now), to: ymd(to) };
|
|
51
|
+
}
|
|
52
|
+
case "next-90": {
|
|
53
|
+
const to = new Date(now);
|
|
54
|
+
to.setUTCDate(to.getUTCDate() + 90);
|
|
55
|
+
return { from: ymd(now), to: ymd(to) };
|
|
56
|
+
}
|
|
57
|
+
case "this-month":
|
|
58
|
+
return { from: ymd(startOfMonth()), to: ymd(endOfMonth()) };
|
|
59
|
+
case "this-quarter":
|
|
60
|
+
return { from: ymd(startOfQuarter()), to: ymd(endOfQuarter()) };
|
|
61
|
+
case "overdue": {
|
|
62
|
+
const yesterday = new Date(now);
|
|
63
|
+
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
|
|
64
|
+
return { from: null, to: ymd(yesterday) };
|
|
65
|
+
}
|
|
66
|
+
default:
|
|
67
|
+
return { from: null, to: null };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function CloseDateFilterPopover({ value, onApply }) {
|
|
71
|
+
const t = useT();
|
|
72
|
+
const [open, setOpen] = React.useState(false);
|
|
73
|
+
const [draft, setDraft] = React.useState(value);
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
if (open) setDraft(value);
|
|
76
|
+
}, [open, value]);
|
|
77
|
+
const isActive = !!(value.from || value.to);
|
|
78
|
+
const chipLabel = translateWithFallback(t, "customers.deals.kanban.filter.close", "Close");
|
|
79
|
+
const chipValue = React.useMemo(() => {
|
|
80
|
+
if (!isActive) return translateWithFallback(t, "customers.deals.kanban.filter.any", "Any");
|
|
81
|
+
if (value.from && value.to) {
|
|
82
|
+
const f = new Date(value.from).toLocaleDateString(void 0, { month: "short", day: "2-digit" });
|
|
83
|
+
const tt = new Date(value.to).toLocaleDateString(void 0, { month: "short", day: "2-digit" });
|
|
84
|
+
return `${f} \u2013 ${tt}`;
|
|
85
|
+
}
|
|
86
|
+
if (value.from && !value.to)
|
|
87
|
+
return translateWithFallback(t, "customers.deals.kanban.filter.from", "From {date}", {
|
|
88
|
+
date: new Date(value.from).toLocaleDateString(void 0, { month: "short", day: "2-digit" })
|
|
89
|
+
});
|
|
90
|
+
if (!value.from && value.to)
|
|
91
|
+
return translateWithFallback(t, "customers.deals.kanban.filter.until", "Until {date}", {
|
|
92
|
+
date: new Date(value.to).toLocaleDateString(void 0, { month: "short", day: "2-digit" })
|
|
93
|
+
});
|
|
94
|
+
return "";
|
|
95
|
+
}, [value.from, value.to, isActive, t]);
|
|
96
|
+
const applyPreset = (key) => setDraft(buildPresetRange(key));
|
|
97
|
+
const handleApply = () => {
|
|
98
|
+
onApply({
|
|
99
|
+
from: draft.from && draft.from.length ? draft.from : null,
|
|
100
|
+
to: draft.to && draft.to.length ? draft.to : null
|
|
101
|
+
});
|
|
102
|
+
setOpen(false);
|
|
103
|
+
};
|
|
104
|
+
const handleClear = () => setDraft({ from: null, to: null });
|
|
105
|
+
const handleKeyDown = (event) => {
|
|
106
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
handleApply();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const hasDraft = !!(draft.from || draft.to);
|
|
112
|
+
const footerLeft = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
113
|
+
/* @__PURE__ */ jsx("span", { children: hasDraft ? translateWithFallback(t, "customers.deals.kanban.filter.dateRange.applied", "Range set") : translateWithFallback(t, "customers.deals.kanban.filter.any", "Any") }),
|
|
114
|
+
hasDraft ? /* @__PURE__ */ jsx(
|
|
115
|
+
Button,
|
|
116
|
+
{
|
|
117
|
+
variant: "link",
|
|
118
|
+
size: "sm",
|
|
119
|
+
type: "button",
|
|
120
|
+
onClick: handleClear,
|
|
121
|
+
className: "h-auto px-0 py-0 text-xs font-medium leading-normal",
|
|
122
|
+
children: translateWithFallback(t, "customers.deals.kanban.filter.clear", "Clear")
|
|
123
|
+
}
|
|
124
|
+
) : null
|
|
125
|
+
] });
|
|
126
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
127
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(ChipButton, { label: chipLabel, value: chipValue, active: isActive }) }),
|
|
128
|
+
/* @__PURE__ */ jsx(
|
|
129
|
+
PopoverContent,
|
|
130
|
+
{
|
|
131
|
+
className: "w-80 rounded-2xl border-border bg-transparent p-0 shadow-xl",
|
|
132
|
+
align: "start",
|
|
133
|
+
onKeyDown: handleKeyDown,
|
|
134
|
+
children: /* @__PURE__ */ jsxs(
|
|
135
|
+
FilterPopoverShell,
|
|
136
|
+
{
|
|
137
|
+
title: translateWithFallback(t, "customers.deals.kanban.filter.close.title", "Filter \xB7 Expected close"),
|
|
138
|
+
onClose: () => setOpen(false),
|
|
139
|
+
onCancel: () => setOpen(false),
|
|
140
|
+
onApply: handleApply,
|
|
141
|
+
footerLeft,
|
|
142
|
+
children: [
|
|
143
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [
|
|
144
|
+
{ key: "next-30", label: translateWithFallback(t, "customers.deals.kanban.filter.preset.next30", "Next 30 days") },
|
|
145
|
+
{ key: "next-90", label: translateWithFallback(t, "customers.deals.kanban.filter.preset.next90", "Next 90 days") },
|
|
146
|
+
{ key: "this-month", label: translateWithFallback(t, "customers.deals.kanban.filter.preset.thisMonth", "This month") },
|
|
147
|
+
{ key: "this-quarter", label: translateWithFallback(t, "customers.deals.kanban.filter.preset.thisQuarter", "This quarter") },
|
|
148
|
+
{ key: "overdue", label: translateWithFallback(t, "customers.deals.kanban.filter.preset.overdue", "Overdue") }
|
|
149
|
+
].map((preset) => /* @__PURE__ */ jsx(
|
|
150
|
+
Button,
|
|
151
|
+
{
|
|
152
|
+
variant: "outline",
|
|
153
|
+
size: "sm",
|
|
154
|
+
type: "button",
|
|
155
|
+
onClick: () => applyPreset(preset.key),
|
|
156
|
+
className: "h-auto rounded-full border-border bg-card px-2.5 py-1 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
157
|
+
children: preset.label
|
|
158
|
+
},
|
|
159
|
+
preset.key
|
|
160
|
+
)) }),
|
|
161
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
162
|
+
/* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: [
|
|
163
|
+
translateWithFallback(t, "customers.deals.kanban.filter.fromLabel", "From"),
|
|
164
|
+
/* @__PURE__ */ jsx(
|
|
165
|
+
"input",
|
|
166
|
+
{
|
|
167
|
+
type: "date",
|
|
168
|
+
value: draft.from ?? "",
|
|
169
|
+
onChange: (event) => setDraft((previous) => ({ ...previous, from: event.target.value || null })),
|
|
170
|
+
className: "rounded-md border border-input bg-card px-2.5 py-1.5 text-sm font-normal leading-normal text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
] }),
|
|
174
|
+
/* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: [
|
|
175
|
+
translateWithFallback(t, "customers.deals.kanban.filter.toLabel", "To"),
|
|
176
|
+
/* @__PURE__ */ jsx(
|
|
177
|
+
"input",
|
|
178
|
+
{
|
|
179
|
+
type: "date",
|
|
180
|
+
value: draft.to ?? "",
|
|
181
|
+
onChange: (event) => setDraft((previous) => ({ ...previous, to: event.target.value || null })),
|
|
182
|
+
className: "rounded-md border border-input bg-card px-2.5 py-1.5 text-sm font-normal leading-normal text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
] })
|
|
186
|
+
] })
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
] });
|
|
193
|
+
}
|
|
194
|
+
var CloseDateFilterPopover_default = CloseDateFilterPopover;
|
|
195
|
+
export {
|
|
196
|
+
CloseDateFilterPopover,
|
|
197
|
+
CloseDateFilterPopover_default as default
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=CloseDateFilterPopover.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/customers/backend/customers/deals/pipeline/components/CloseDateFilterPopover.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@open-mercato/ui/primitives/popover'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { ChipButton } from './ChipButton'\nimport { FilterPopoverShell } from './FilterPopoverShell'\n\nexport type CloseDateRange = {\n from: string | null\n to: string | null\n}\n\ntype CloseDateFilterPopoverProps = {\n value: CloseDateRange\n onApply: (next: CloseDateRange) => void\n}\n\ntype PresetKey = 'next-30' | 'next-90' | 'this-month' | 'this-quarter' | 'overdue' | 'custom'\n\nfunction ymd(date: Date): string {\n return date.toISOString().slice(0, 10)\n}\n\nfunction startOfMonth(): Date {\n const d = new Date()\n d.setUTCHours(0, 0, 0, 0)\n d.setUTCDate(1)\n return d\n}\n\nfunction endOfMonth(): Date {\n const d = startOfMonth()\n d.setUTCMonth(d.getUTCMonth() + 1)\n d.setUTCDate(0)\n return d\n}\n\nfunction startOfQuarter(): Date {\n const d = new Date()\n d.setUTCHours(0, 0, 0, 0)\n d.setUTCDate(1)\n const m = d.getUTCMonth()\n d.setUTCMonth(m - (m % 3))\n return d\n}\n\nfunction endOfQuarter(): Date {\n const d = startOfQuarter()\n d.setUTCMonth(d.getUTCMonth() + 3)\n d.setUTCDate(0)\n return d\n}\n\nfunction buildPresetRange(key: PresetKey): CloseDateRange {\n const now = new Date()\n now.setUTCHours(0, 0, 0, 0)\n switch (key) {\n case 'next-30': {\n const to = new Date(now)\n to.setUTCDate(to.getUTCDate() + 30)\n return { from: ymd(now), to: ymd(to) }\n }\n case 'next-90': {\n const to = new Date(now)\n to.setUTCDate(to.getUTCDate() + 90)\n return { from: ymd(now), to: ymd(to) }\n }\n case 'this-month':\n return { from: ymd(startOfMonth()), to: ymd(endOfMonth()) }\n case 'this-quarter':\n return { from: ymd(startOfQuarter()), to: ymd(endOfQuarter()) }\n case 'overdue': {\n const yesterday = new Date(now)\n yesterday.setUTCDate(yesterday.getUTCDate() - 1)\n return { from: null, to: ymd(yesterday) }\n }\n default:\n return { from: null, to: null }\n }\n}\n\nexport function CloseDateFilterPopover({ value, onApply }: CloseDateFilterPopoverProps): React.ReactElement {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [draft, setDraft] = React.useState<CloseDateRange>(value)\n\n React.useEffect(() => {\n if (open) setDraft(value)\n }, [open, value])\n\n const isActive = !!(value.from || value.to)\n const chipLabel = translateWithFallback(t, 'customers.deals.kanban.filter.close', 'Close')\n const chipValue = React.useMemo(() => {\n // Show \"Any\" (not \"Next 90 days\") when no filter is applied \u2014 the previous label was a\n // preset name, which read like the filter was already active and confused operators.\n if (!isActive) return translateWithFallback(t, 'customers.deals.kanban.filter.any', 'Any')\n if (value.from && value.to) {\n const f = new Date(value.from).toLocaleDateString(undefined, { month: 'short', day: '2-digit' })\n const tt = new Date(value.to).toLocaleDateString(undefined, { month: 'short', day: '2-digit' })\n return `${f} \u2013 ${tt}`\n }\n if (value.from && !value.to)\n return translateWithFallback(t, 'customers.deals.kanban.filter.from', 'From {date}', {\n date: new Date(value.from).toLocaleDateString(undefined, { month: 'short', day: '2-digit' }),\n })\n if (!value.from && value.to)\n return translateWithFallback(t, 'customers.deals.kanban.filter.until', 'Until {date}', {\n date: new Date(value.to).toLocaleDateString(undefined, { month: 'short', day: '2-digit' }),\n })\n return ''\n }, [value.from, value.to, isActive, t])\n\n const applyPreset = (key: PresetKey) => setDraft(buildPresetRange(key))\n\n const handleApply = () => {\n onApply({\n from: draft.from && draft.from.length ? draft.from : null,\n to: draft.to && draft.to.length ? draft.to : null,\n })\n setOpen(false)\n }\n const handleClear = () => setDraft({ from: null, to: null })\n\n // Cmd/Ctrl+Enter from anywhere inside the popover confirms \u2014 parity with the dialog\n // primary-action shortcut (`AGENTS.md` UI Interaction rules).\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleApply()\n }\n }\n\n const hasDraft = !!(draft.from || draft.to)\n const footerLeft = (\n <div className=\"flex items-center gap-3\">\n <span>\n {hasDraft\n ? translateWithFallback(t, 'customers.deals.kanban.filter.dateRange.applied', 'Range set')\n : translateWithFallback(t, 'customers.deals.kanban.filter.any', 'Any')}\n </span>\n {hasDraft ? (\n <Button\n variant=\"link\"\n size=\"sm\"\n type=\"button\"\n onClick={handleClear}\n className=\"h-auto px-0 py-0 text-xs font-medium leading-normal\"\n >\n {translateWithFallback(t, 'customers.deals.kanban.filter.clear', 'Clear')}\n </Button>\n ) : null}\n </div>\n )\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <ChipButton label={chipLabel} value={chipValue} active={isActive} />\n </PopoverTrigger>\n <PopoverContent\n className=\"w-80 rounded-2xl border-border bg-transparent p-0 shadow-xl\"\n align=\"start\"\n onKeyDown={handleKeyDown}\n >\n <FilterPopoverShell\n title={translateWithFallback(t, 'customers.deals.kanban.filter.close.title', 'Filter \u00B7 Expected close')}\n onClose={() => setOpen(false)}\n onCancel={() => setOpen(false)}\n onApply={handleApply}\n footerLeft={footerLeft}\n >\n <div className=\"flex flex-wrap gap-1.5\">\n {(\n [\n { key: 'next-30', label: translateWithFallback(t, 'customers.deals.kanban.filter.preset.next30', 'Next 30 days') },\n { key: 'next-90', label: translateWithFallback(t, 'customers.deals.kanban.filter.preset.next90', 'Next 90 days') },\n { key: 'this-month', label: translateWithFallback(t, 'customers.deals.kanban.filter.preset.thisMonth', 'This month') },\n { key: 'this-quarter', label: translateWithFallback(t, 'customers.deals.kanban.filter.preset.thisQuarter', 'This quarter') },\n { key: 'overdue', label: translateWithFallback(t, 'customers.deals.kanban.filter.preset.overdue', 'Overdue') },\n ] as Array<{ key: PresetKey; label: string }>\n ).map((preset) => (\n <Button\n variant=\"outline\"\n size=\"sm\"\n key={preset.key}\n type=\"button\"\n onClick={() => applyPreset(preset.key)}\n className=\"h-auto rounded-full border-border bg-card px-2.5 py-1 text-xs font-medium text-muted-foreground hover:bg-muted hover:text-foreground\"\n >\n {preset.label}\n </Button>\n ))}\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <label className=\"flex flex-col gap-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {translateWithFallback(t, 'customers.deals.kanban.filter.fromLabel', 'From')}\n <input\n type=\"date\"\n value={draft.from ?? ''}\n onChange={(event) =>\n setDraft((previous) => ({ ...previous, from: event.target.value || null }))\n }\n className=\"rounded-md border border-input bg-card px-2.5 py-1.5 text-sm font-normal leading-normal text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n </label>\n <label className=\"flex flex-col gap-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {translateWithFallback(t, 'customers.deals.kanban.filter.toLabel', 'To')}\n <input\n type=\"date\"\n value={draft.to ?? ''}\n onChange={(event) =>\n setDraft((previous) => ({ ...previous, to: event.target.value || null }))\n }\n className=\"rounded-md border border-input bg-card px-2.5 py-1.5 text-sm font-normal leading-normal text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n </label>\n </div>\n </FilterPopoverShell>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport default CloseDateFilterPopover\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/modules/customers/backend/customers/deals/pipeline/components/CurrencyBreakdownTable.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AlertTriangle } from "lucide-react";
|
|
4
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
|
+
import { translateWithFallback } from "@open-mercato/shared/lib/i18n/translate";
|
|
6
|
+
function CurrencyBreakdownTable({
|
|
7
|
+
rows,
|
|
8
|
+
baseCurrencyCode,
|
|
9
|
+
missingRateCurrencies
|
|
10
|
+
}) {
|
|
11
|
+
const t = useT();
|
|
12
|
+
const base = baseCurrencyCode?.toUpperCase() ?? null;
|
|
13
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col bg-card px-6", children: [
|
|
14
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 pt-5 pb-2", children: [
|
|
15
|
+
/* @__PURE__ */ jsx("span", { className: "text-overline font-semibold uppercase tracking-wider text-muted-foreground", children: translateWithFallback(t, "customers.deals.kanban.currencyBreakdown.colCurrency", "Currency") }),
|
|
16
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1", "aria-hidden": "true" }),
|
|
17
|
+
/* @__PURE__ */ jsx("span", { className: "text-overline font-semibold uppercase tracking-wider text-muted-foreground", children: translateWithFallback(
|
|
18
|
+
t,
|
|
19
|
+
"customers.deals.kanban.currencyBreakdown.colNativeTotal",
|
|
20
|
+
"Native total"
|
|
21
|
+
) }),
|
|
22
|
+
/* @__PURE__ */ jsx("span", { className: "pl-6 text-overline font-semibold uppercase tracking-wider text-muted-foreground", children: translateWithFallback(t, "customers.deals.kanban.currencyBreakdown.colDeals", "Deals") })
|
|
23
|
+
] }),
|
|
24
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border", "aria-hidden": "true" }),
|
|
25
|
+
/* @__PURE__ */ jsx("ul", { className: "flex flex-col", children: rows.map((row) => {
|
|
26
|
+
const code = row.currency.toUpperCase();
|
|
27
|
+
const isBase = base !== null && code === base;
|
|
28
|
+
const isMissingRate = missingRateCurrencies.includes(row.currency);
|
|
29
|
+
return /* @__PURE__ */ jsxs(
|
|
30
|
+
"li",
|
|
31
|
+
{
|
|
32
|
+
className: "flex items-center gap-1 border-b border-border py-2.5 last:border-b-0",
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-sm font-semibold leading-normal text-foreground", children: [
|
|
35
|
+
isMissingRate ? /* @__PURE__ */ jsx(
|
|
36
|
+
AlertTriangle,
|
|
37
|
+
{
|
|
38
|
+
className: "size-3.5 text-status-warning-icon",
|
|
39
|
+
"aria-hidden": "true"
|
|
40
|
+
}
|
|
41
|
+
) : null,
|
|
42
|
+
/* @__PURE__ */ jsx("span", { children: code }),
|
|
43
|
+
isBase ? (
|
|
44
|
+
// text-[9px] is an intentional Figma exception for the BASE badge (node 1251:671).
|
|
45
|
+
// See CurrencyFilterPopover for the same rationale — both share the badge.
|
|
46
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-sm bg-brand-violet/10 px-1.5 py-px text-[9px] font-bold uppercase leading-normal tracking-wider text-brand-violet", children: translateWithFallback(t, "customers.deals.kanban.currencyBreakdown.basePill", "BASE") })
|
|
47
|
+
) : null
|
|
48
|
+
] }),
|
|
49
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1", "aria-hidden": "true" }),
|
|
50
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold leading-normal text-foreground tabular-nums", children: formatAmount(row.total) }),
|
|
51
|
+
/* @__PURE__ */ jsx("span", { className: "pl-6 text-sm font-normal leading-normal text-muted-foreground tabular-nums", children: row.count })
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
row.currency
|
|
55
|
+
);
|
|
56
|
+
}) })
|
|
57
|
+
] });
|
|
58
|
+
}
|
|
59
|
+
function formatAmount(amount) {
|
|
60
|
+
return new Intl.NumberFormat(void 0, {
|
|
61
|
+
style: "decimal",
|
|
62
|
+
maximumFractionDigits: 0,
|
|
63
|
+
useGrouping: true
|
|
64
|
+
}).format(Math.round(amount));
|
|
65
|
+
}
|
|
66
|
+
var CurrencyBreakdownTable_default = CurrencyBreakdownTable;
|
|
67
|
+
export {
|
|
68
|
+
CurrencyBreakdownTable,
|
|
69
|
+
CurrencyBreakdownTable_default as default
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=CurrencyBreakdownTable.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/customers/backend/customers/deals/pipeline/components/CurrencyBreakdownTable.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { AlertTriangle } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\n\nexport type CurrencyBreakdownRow = {\n currency: string\n total: number\n count: number\n}\n\ntype CurrencyBreakdownTableProps = {\n rows: CurrencyBreakdownRow[]\n baseCurrencyCode: string | null\n /** Currencies with no FX rate to base \u2014 surfaced with a warning icon next to the code. */\n missingRateCurrencies: string[]\n}\n\n/**\n * Read-only currency breakdown body shared between `LaneCurrencyBreakdown` and any future\n * informational popover. Matches the SPEC-048 Figma node 1251:610 currency table:\n * - Column header row: \"Currency \u00B7 Native total \u00B7 Deals\" (overline gray)\n * - Divider line\n * - Per-currency rows with thin bottom dividers; BASE pill on the base-currency row\n *\n * The interactive filter variant (`CurrencyFilterPopover`) does NOT use this table \u2014 its rows\n * are radio selectors with different highlighting \u2014 but it reuses the same outer chrome from\n * `LaneCurrencyBreakdown`. Keeping them visually consistent is the responsibility of the\n * Figma source of truth, not of this component.\n */\nexport function CurrencyBreakdownTable({\n rows,\n baseCurrencyCode,\n missingRateCurrencies,\n}: CurrencyBreakdownTableProps): React.ReactElement {\n const t = useT()\n const base = baseCurrencyCode?.toUpperCase() ?? null\n\n return (\n <div className=\"flex flex-col bg-card px-6\">\n <div className=\"flex items-center gap-1 pt-5 pb-2\">\n <span className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground\">\n {translateWithFallback(t, 'customers.deals.kanban.currencyBreakdown.colCurrency', 'Currency')}\n </span>\n <span className=\"flex-1\" aria-hidden=\"true\" />\n <span className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground\">\n {translateWithFallback(\n t,\n 'customers.deals.kanban.currencyBreakdown.colNativeTotal',\n 'Native total',\n )}\n </span>\n <span className=\"pl-6 text-overline font-semibold uppercase tracking-wider text-muted-foreground\">\n {translateWithFallback(t, 'customers.deals.kanban.currencyBreakdown.colDeals', 'Deals')}\n </span>\n </div>\n <div className=\"border-b border-border\" aria-hidden=\"true\" />\n\n <ul className=\"flex flex-col\">\n {rows.map((row) => {\n const code = row.currency.toUpperCase()\n const isBase = base !== null && code === base\n const isMissingRate = missingRateCurrencies.includes(row.currency)\n return (\n <li\n key={row.currency}\n className=\"flex items-center gap-1 border-b border-border py-2.5 last:border-b-0\"\n >\n <span className=\"flex items-center gap-1.5 text-sm font-semibold leading-normal text-foreground\">\n {isMissingRate ? (\n <AlertTriangle\n className=\"size-3.5 text-status-warning-icon\"\n aria-hidden=\"true\"\n />\n ) : null}\n <span>{code}</span>\n {isBase ? (\n // text-[9px] is an intentional Figma exception for the BASE badge (node 1251:671).\n // See CurrencyFilterPopover for the same rationale \u2014 both share the badge.\n <span className=\"inline-flex items-center rounded-sm bg-brand-violet/10 px-1.5 py-px text-[9px] font-bold uppercase leading-normal tracking-wider text-brand-violet\">\n {translateWithFallback(t, 'customers.deals.kanban.currencyBreakdown.basePill', 'BASE')}\n </span>\n ) : null}\n </span>\n <span className=\"flex-1\" aria-hidden=\"true\" />\n <span className=\"text-sm font-semibold leading-normal text-foreground tabular-nums\">\n {formatAmount(row.total)}\n </span>\n <span className=\"pl-6 text-sm font-normal leading-normal text-muted-foreground tabular-nums\">\n {row.count}\n </span>\n </li>\n )\n })}\n </ul>\n </div>\n )\n}\n\nfunction formatAmount(amount: number): string {\n return new Intl.NumberFormat(undefined, {\n style: 'decimal',\n maximumFractionDigits: 0,\n useGrouping: true,\n }).format(Math.round(amount))\n}\n\nexport default CurrencyBreakdownTable\n"],
|
|
5
|
+
"mappings": ";AA0CM,SACE,KADF;AAvCN,SAAS,qBAAqB;AAC9B,SAAS,YAAY;AACrB,SAAS,6BAA6B;AA2B/B,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACF,GAAoD;AAClD,QAAM,IAAI,KAAK;AACf,QAAM,OAAO,kBAAkB,YAAY,KAAK;AAEhD,SACE,qBAAC,SAAI,WAAU,8BACb;AAAA,yBAAC,SAAI,WAAU,qCACb;AAAA,0BAAC,UAAK,WAAU,8EACb,gCAAsB,GAAG,wDAAwD,UAAU,GAC9F;AAAA,MACA,oBAAC,UAAK,WAAU,UAAS,eAAY,QAAO;AAAA,MAC5C,oBAAC,UAAK,WAAU,8EACb;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,MACF,GACF;AAAA,MACA,oBAAC,UAAK,WAAU,mFACb,gCAAsB,GAAG,qDAAqD,OAAO,GACxF;AAAA,OACF;AAAA,IACA,oBAAC,SAAI,WAAU,0BAAyB,eAAY,QAAO;AAAA,IAE3D,oBAAC,QAAG,WAAU,iBACX,eAAK,IAAI,CAAC,QAAQ;AACjB,YAAM,OAAO,IAAI,SAAS,YAAY;AACtC,YAAM,SAAS,SAAS,QAAQ,SAAS;AACzC,YAAM,gBAAgB,sBAAsB,SAAS,IAAI,QAAQ;AACjE,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UAEV;AAAA,iCAAC,UAAK,WAAU,kFACb;AAAA,8BACC;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,eAAY;AAAA;AAAA,cACd,IACE;AAAA,cACJ,oBAAC,UAAM,gBAAK;AAAA,cACX;AAAA;AAAA;AAAA,gBAGC,oBAAC,UAAK,WAAU,sJACb,gCAAsB,GAAG,qDAAqD,MAAM,GACvF;AAAA,kBACE;AAAA,eACN;AAAA,YACA,oBAAC,UAAK,WAAU,UAAS,eAAY,QAAO;AAAA,YAC5C,oBAAC,UAAK,WAAU,qEACb,uBAAa,IAAI,KAAK,GACzB;AAAA,YACA,oBAAC,UAAK,WAAU,8EACb,cAAI,OACP;AAAA;AAAA;AAAA,QAzBK,IAAI;AAAA,MA0BX;AAAA,IAEJ,CAAC,GACH;AAAA,KACF;AAEJ;AAEA,SAAS,aAAa,QAAwB;AAC5C,SAAO,IAAI,KAAK,aAAa,QAAW;AAAA,IACtC,OAAO;AAAA,IACP,uBAAuB;AAAA,IACvB,aAAa;AAAA,EACf,CAAC,EAAE,OAAO,KAAK,MAAM,MAAM,CAAC;AAC9B;AAEA,IAAO,iCAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/modules/customers/backend/customers/deals/pipeline/components/CurrencyFilterPopover.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { X } from "lucide-react";
|
|
5
|
+
import {
|
|
6
|
+
Popover,
|
|
7
|
+
PopoverContent,
|
|
8
|
+
PopoverTrigger
|
|
9
|
+
} from "@open-mercato/ui/primitives/popover";
|
|
10
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
11
|
+
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
12
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
13
|
+
import { translateWithFallback } from "@open-mercato/shared/lib/i18n/translate";
|
|
14
|
+
import { ChipButton } from "./ChipButton.js";
|
|
15
|
+
function CurrencyFilterPopover({
|
|
16
|
+
rows,
|
|
17
|
+
baseCurrencyCode,
|
|
18
|
+
totalInBaseCurrency,
|
|
19
|
+
headingLabel,
|
|
20
|
+
headingCount,
|
|
21
|
+
selectedCurrency,
|
|
22
|
+
onApply
|
|
23
|
+
}) {
|
|
24
|
+
const t = useT();
|
|
25
|
+
const [open, setOpen] = React.useState(false);
|
|
26
|
+
const [draft, setDraft] = React.useState(selectedCurrency);
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
if (open) setDraft(selectedCurrency);
|
|
29
|
+
}, [open, selectedCurrency]);
|
|
30
|
+
const base = baseCurrencyCode?.toUpperCase() ?? null;
|
|
31
|
+
const chipLabel = translateWithFallback(t, "customers.deals.kanban.currencyFilter.chipLabel", "Currency");
|
|
32
|
+
const chipValue = selectedCurrency ? selectedCurrency.toUpperCase() : translateWithFallback(t, "customers.deals.kanban.filter.all", "All");
|
|
33
|
+
const closeLabel = translateWithFallback(t, "customers.deals.kanban.filter.close", "Close");
|
|
34
|
+
const applyLabel = translateWithFallback(
|
|
35
|
+
t,
|
|
36
|
+
"customers.deals.kanban.currencyFilter.apply",
|
|
37
|
+
"Apply filter"
|
|
38
|
+
);
|
|
39
|
+
const handleApply = () => {
|
|
40
|
+
onApply(draft);
|
|
41
|
+
setOpen(false);
|
|
42
|
+
};
|
|
43
|
+
const handleKeyDown = (event) => {
|
|
44
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
handleApply();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const headingCountLabel = translateWithFallback(
|
|
50
|
+
t,
|
|
51
|
+
"customers.deals.kanban.currencyBreakdown.headingCount",
|
|
52
|
+
"({count} deals)",
|
|
53
|
+
{ count: headingCount }
|
|
54
|
+
);
|
|
55
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
56
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
57
|
+
ChipButton,
|
|
58
|
+
{
|
|
59
|
+
label: chipLabel,
|
|
60
|
+
value: chipValue,
|
|
61
|
+
active: selectedCurrency !== null
|
|
62
|
+
}
|
|
63
|
+
) }),
|
|
64
|
+
/* @__PURE__ */ jsx(
|
|
65
|
+
PopoverContent,
|
|
66
|
+
{
|
|
67
|
+
align: "start",
|
|
68
|
+
className: "w-96 rounded-2xl border-border bg-transparent p-0 shadow-xl",
|
|
69
|
+
onKeyDown: handleKeyDown,
|
|
70
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col overflow-hidden rounded-2xl bg-card", children: [
|
|
71
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border bg-card px-5 py-4", children: [
|
|
72
|
+
/* @__PURE__ */ jsx("span", { className: "text-base font-bold leading-normal text-foreground", children: translateWithFallback(
|
|
73
|
+
t,
|
|
74
|
+
"customers.deals.kanban.currencyFilter.title",
|
|
75
|
+
"Filter kanban by currency"
|
|
76
|
+
) }),
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
IconButton,
|
|
79
|
+
{
|
|
80
|
+
variant: "ghost",
|
|
81
|
+
size: "xs",
|
|
82
|
+
onClick: () => setOpen(false),
|
|
83
|
+
"aria-label": closeLabel,
|
|
84
|
+
className: "flex size-7 items-center justify-center rounded-md border border-border bg-card text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
85
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3.5", "aria-hidden": "true" })
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
] }),
|
|
89
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col bg-card pt-3.5", children: [
|
|
90
|
+
/* @__PURE__ */ jsx("div", { className: "px-6 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-overline font-semibold uppercase tracking-wider text-muted-foreground", children: translateWithFallback(
|
|
91
|
+
t,
|
|
92
|
+
"customers.deals.kanban.currencyFilter.subtitle",
|
|
93
|
+
"{label} \xB7 {count} deals \xB7 {total} {currency}",
|
|
94
|
+
{
|
|
95
|
+
label: headingLabel,
|
|
96
|
+
count: headingCount,
|
|
97
|
+
total: formatAmount(totalInBaseCurrency),
|
|
98
|
+
currency: baseCurrencyCode ?? ""
|
|
99
|
+
}
|
|
100
|
+
) }) }),
|
|
101
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
102
|
+
/* @__PURE__ */ jsx(
|
|
103
|
+
CurrencyRow,
|
|
104
|
+
{
|
|
105
|
+
isAllCurrencies: true,
|
|
106
|
+
selected: draft === null,
|
|
107
|
+
onClick: () => setDraft(null),
|
|
108
|
+
label: translateWithFallback(
|
|
109
|
+
t,
|
|
110
|
+
"customers.deals.kanban.currencyFilter.allCurrencies",
|
|
111
|
+
"All currencies"
|
|
112
|
+
),
|
|
113
|
+
amount: totalInBaseCurrency,
|
|
114
|
+
amountCurrency: baseCurrencyCode ?? "",
|
|
115
|
+
count: headingCount
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
rows.map((row) => {
|
|
119
|
+
const code = row.currency.toUpperCase();
|
|
120
|
+
const isBase = base !== null && code === base;
|
|
121
|
+
const isSelected = draft === code;
|
|
122
|
+
return /* @__PURE__ */ jsx(
|
|
123
|
+
CurrencyRow,
|
|
124
|
+
{
|
|
125
|
+
selected: isSelected,
|
|
126
|
+
onClick: () => setDraft(code),
|
|
127
|
+
label: code,
|
|
128
|
+
showBasePill: isBase,
|
|
129
|
+
amount: row.total,
|
|
130
|
+
amountCurrency: code,
|
|
131
|
+
count: row.count
|
|
132
|
+
},
|
|
133
|
+
row.currency
|
|
134
|
+
);
|
|
135
|
+
})
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsx("div", { className: "px-6 pt-3 pb-3.5", children: /* @__PURE__ */ jsx("p", { className: "text-xs font-normal leading-normal text-muted-foreground", children: translateWithFallback(
|
|
138
|
+
t,
|
|
139
|
+
"customers.deals.kanban.currencyFilter.footerCredit",
|
|
140
|
+
"NBP mid conversion rate \xB7 {date}",
|
|
141
|
+
{ date: formatToday() }
|
|
142
|
+
) }) })
|
|
143
|
+
] }),
|
|
144
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-end border-t border-border bg-muted px-5 py-3.5", children: /* @__PURE__ */ jsx(
|
|
145
|
+
Button,
|
|
146
|
+
{
|
|
147
|
+
type: "button",
|
|
148
|
+
onClick: handleApply,
|
|
149
|
+
className: "h-auto rounded-lg bg-foreground px-5 py-2 text-sm font-semibold text-background hover:bg-foreground/90",
|
|
150
|
+
children: applyLabel
|
|
151
|
+
}
|
|
152
|
+
) })
|
|
153
|
+
] })
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
] });
|
|
157
|
+
}
|
|
158
|
+
function CurrencyRow({
|
|
159
|
+
isAllCurrencies = false,
|
|
160
|
+
selected,
|
|
161
|
+
onClick,
|
|
162
|
+
label,
|
|
163
|
+
showBasePill = false,
|
|
164
|
+
amount,
|
|
165
|
+
amountCurrency,
|
|
166
|
+
count
|
|
167
|
+
}) {
|
|
168
|
+
const t = useT();
|
|
169
|
+
const rowBg = selected ? "bg-brand-violet/10" : "bg-card hover:bg-muted";
|
|
170
|
+
const codeColor = selected ? "text-brand-violet" : "text-foreground";
|
|
171
|
+
const amountColor = selected ? "text-brand-violet font-semibold" : "text-foreground font-normal";
|
|
172
|
+
const currencyColor = selected ? "text-brand-violet/80" : "text-muted-foreground";
|
|
173
|
+
const countColor = selected ? "text-brand-violet font-semibold" : "text-foreground/80 font-normal";
|
|
174
|
+
return /* @__PURE__ */ jsxs(
|
|
175
|
+
Button,
|
|
176
|
+
{
|
|
177
|
+
type: "button",
|
|
178
|
+
variant: "ghost",
|
|
179
|
+
role: "radio",
|
|
180
|
+
"aria-checked": selected,
|
|
181
|
+
onClick,
|
|
182
|
+
className: `h-auto w-full justify-start gap-3 rounded-none px-6 py-2.5 text-left font-normal ${rowBg}`,
|
|
183
|
+
children: [
|
|
184
|
+
/* @__PURE__ */ jsx(RadioDot, { selected }),
|
|
185
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
|
|
186
|
+
/* @__PURE__ */ jsx("span", { className: `text-sm leading-normal ${codeColor} ${selected ? "font-semibold" : "font-normal"}`, children: label }),
|
|
187
|
+
showBasePill ? (
|
|
188
|
+
// text-[9px] is an intentional Figma exception for the BASE badge (node 1251:671).
|
|
189
|
+
// The DS scale bottoms out at text-xs (12px); this 9px is design-system tracked as
|
|
190
|
+
// a tiny-badge exception and should not be migrated to text-overline.
|
|
191
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-sm bg-muted px-1.5 py-px text-[9px] font-bold uppercase leading-normal tracking-wider text-muted-foreground", children: translateWithFallback(t, "customers.deals.kanban.currencyBreakdown.basePill", "BASE") })
|
|
192
|
+
) : null
|
|
193
|
+
] }),
|
|
194
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1", "aria-hidden": "true" }),
|
|
195
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-baseline gap-1", children: [
|
|
196
|
+
/* @__PURE__ */ jsx("span", { className: `text-sm leading-normal tabular-nums ${amountColor}`, children: formatAmount(amount) }),
|
|
197
|
+
!isAllCurrencies || amountCurrency ? /* @__PURE__ */ jsx("span", { className: `text-xs leading-normal ${currencyColor}`, children: amountCurrency }) : null
|
|
198
|
+
] }),
|
|
199
|
+
/* @__PURE__ */ jsx("span", { className: `pl-5 text-xs leading-normal tabular-nums ${countColor}`, children: count })
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
function RadioDot({ selected }) {
|
|
205
|
+
return /* @__PURE__ */ jsx(
|
|
206
|
+
"span",
|
|
207
|
+
{
|
|
208
|
+
className: `flex size-4 shrink-0 items-center justify-center rounded-full border ${selected ? "border-brand-violet" : "border-input bg-card"}`,
|
|
209
|
+
"aria-hidden": "true",
|
|
210
|
+
children: selected ? /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-brand-violet" }) : null
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
function formatAmount(amount) {
|
|
215
|
+
return new Intl.NumberFormat(void 0, {
|
|
216
|
+
style: "decimal",
|
|
217
|
+
maximumFractionDigits: 0,
|
|
218
|
+
useGrouping: true
|
|
219
|
+
}).format(Math.round(amount));
|
|
220
|
+
}
|
|
221
|
+
function formatToday() {
|
|
222
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
223
|
+
year: "numeric",
|
|
224
|
+
month: "short",
|
|
225
|
+
day: "2-digit"
|
|
226
|
+
}).format(/* @__PURE__ */ new Date());
|
|
227
|
+
}
|
|
228
|
+
var CurrencyFilterPopover_default = CurrencyFilterPopover;
|
|
229
|
+
export {
|
|
230
|
+
CurrencyFilterPopover,
|
|
231
|
+
CurrencyFilterPopover_default as default
|
|
232
|
+
};
|
|
233
|
+
//# sourceMappingURL=CurrencyFilterPopover.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/customers/backend/customers/deals/pipeline/components/CurrencyFilterPopover.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { X } from 'lucide-react'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@open-mercato/ui/primitives/popover'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { ChipButton } from './ChipButton'\n\nexport type CurrencyFilterRow = {\n currency: string\n total: number\n count: number\n}\n\ntype CurrencyFilterPopoverProps = {\n /** Per-currency rows scoped to the current pipeline filter context. */\n rows: CurrencyFilterRow[]\n /** Tenant base currency code, or `null` when none is configured. */\n baseCurrencyCode: string | null\n /** Aggregate total across all currencies in `rows`, expressed in `baseCurrencyCode`. */\n totalInBaseCurrency: number\n /** Stage / pipeline label shown in the subtitle (e.g. `\"PIPELINE\"`). */\n headingLabel: string\n /** Total deal count surfaced in the subtitle. */\n headingCount: number\n /** Currently-applied currency filter (`null` means \"All currencies\"). */\n selectedCurrency: string | null\n /** Called when the operator presses \"Apply filter\". `null` clears the filter. */\n onApply: (currency: string | null) => void\n}\n\n/**\n * Currency filter popover anchored to a \"Currency\" chip on the kanban filter bar.\n *\n * Matches SPEC-048 Figma node 1251:699 \u2014 header + subtitle + radio rows + Apply filter footer.\n * Selecting a row narrows the kanban board to deals whose `valueCurrency` equals the chosen\n * code; the \"All currencies\" sentinel clears the filter. The selected row uses the\n * brand-violet tinted background to mirror the Figma highlight.\n *\n * Chrome (rounded-2xl outer, bordered header w/ close, muted footer) matches\n * `LaneCurrencyBreakdown` so the two popovers feel like a family.\n */\nexport function CurrencyFilterPopover({\n rows,\n baseCurrencyCode,\n totalInBaseCurrency,\n headingLabel,\n headingCount,\n selectedCurrency,\n onApply,\n}: CurrencyFilterPopoverProps): React.ReactElement {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [draft, setDraft] = React.useState<string | null>(selectedCurrency)\n\n React.useEffect(() => {\n if (open) setDraft(selectedCurrency)\n }, [open, selectedCurrency])\n\n const base = baseCurrencyCode?.toUpperCase() ?? null\n const chipLabel = translateWithFallback(t, 'customers.deals.kanban.currencyFilter.chipLabel', 'Currency')\n const chipValue = selectedCurrency\n ? selectedCurrency.toUpperCase()\n : translateWithFallback(t, 'customers.deals.kanban.filter.all', 'All')\n const closeLabel = translateWithFallback(t, 'customers.deals.kanban.filter.close', 'Close')\n const applyLabel = translateWithFallback(\n t,\n 'customers.deals.kanban.currencyFilter.apply',\n 'Apply filter',\n )\n\n const handleApply = () => {\n onApply(draft)\n setOpen(false)\n }\n\n // Cmd/Ctrl+Enter parity with other kanban filter popovers (`AGENTS.md` UI Interaction rules).\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleApply()\n }\n }\n\n const headingCountLabel = translateWithFallback(\n t,\n 'customers.deals.kanban.currencyBreakdown.headingCount',\n '({count} deals)',\n { count: headingCount },\n )\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <ChipButton\n label={chipLabel}\n value={chipValue}\n active={selectedCurrency !== null}\n />\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"w-96 rounded-2xl border-border bg-transparent p-0 shadow-xl\"\n onKeyDown={handleKeyDown}\n >\n {/*\n Solid `bg-card` (no opacity modifier) on the outer wrapper \u2014 round-2 UX review item 33.\n Floating overlays must not let underlying card content bleed through; the previous\n `bg-muted/30` allowed card titles/badges to show through this popover.\n */}\n <div className=\"flex flex-col overflow-hidden rounded-2xl bg-card\">\n <div className=\"flex items-center justify-between border-b border-border bg-card px-5 py-4\">\n <span className=\"text-base font-bold leading-normal text-foreground\">\n {translateWithFallback(\n t,\n 'customers.deals.kanban.currencyFilter.title',\n 'Filter kanban by currency',\n )}\n </span>\n <IconButton\n variant=\"ghost\"\n size=\"xs\"\n onClick={() => setOpen(false)}\n aria-label={closeLabel}\n className=\"flex size-7 items-center justify-center rounded-md border border-border bg-card text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n >\n <X className=\"size-3.5\" aria-hidden=\"true\" />\n </IconButton>\n </div>\n\n <div className=\"flex flex-col bg-card pt-3.5\">\n <div className=\"px-6 pb-2\">\n <span className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground\">\n {translateWithFallback(\n t,\n 'customers.deals.kanban.currencyFilter.subtitle',\n '{label} \u00B7 {count} deals \u00B7 {total} {currency}',\n {\n label: headingLabel,\n count: headingCount,\n total: formatAmount(totalInBaseCurrency),\n currency: baseCurrencyCode ?? '',\n },\n )}\n </span>\n </div>\n\n <div className=\"flex flex-col\">\n <CurrencyRow\n isAllCurrencies\n selected={draft === null}\n onClick={() => setDraft(null)}\n label={translateWithFallback(\n t,\n 'customers.deals.kanban.currencyFilter.allCurrencies',\n 'All currencies',\n )}\n amount={totalInBaseCurrency}\n amountCurrency={baseCurrencyCode ?? ''}\n count={headingCount}\n />\n {rows.map((row) => {\n const code = row.currency.toUpperCase()\n const isBase = base !== null && code === base\n const isSelected = draft === code\n return (\n <CurrencyRow\n key={row.currency}\n selected={isSelected}\n onClick={() => setDraft(code)}\n label={code}\n showBasePill={isBase}\n amount={row.total}\n amountCurrency={code}\n count={row.count}\n />\n )\n })}\n </div>\n\n <div className=\"px-6 pt-3 pb-3.5\">\n <p className=\"text-xs font-normal leading-normal text-muted-foreground\">\n {translateWithFallback(\n t,\n 'customers.deals.kanban.currencyFilter.footerCredit',\n 'NBP mid conversion rate \u00B7 {date}',\n { date: formatToday() },\n )}\n </p>\n </div>\n </div>\n\n {/* Footer uses solid `bg-muted` (no opacity modifier) \u2014 round-2 UX review item 33. */}\n <div className=\"flex items-center justify-end border-t border-border bg-muted px-5 py-3.5\">\n <Button\n type=\"button\"\n onClick={handleApply}\n className=\"h-auto rounded-lg bg-foreground px-5 py-2 text-sm font-semibold text-background hover:bg-foreground/90\"\n >\n {applyLabel}\n </Button>\n </div>\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\ntype CurrencyRowProps = {\n /** Marks the row as the \"All currencies\" sentinel so we suppress the per-row currency suffix. */\n isAllCurrencies?: boolean\n selected: boolean\n onClick: () => void\n label: string\n showBasePill?: boolean\n amount: number\n amountCurrency: string\n count: number\n}\n\nfunction CurrencyRow({\n isAllCurrencies = false,\n selected,\n onClick,\n label,\n showBasePill = false,\n amount,\n amountCurrency,\n count,\n}: CurrencyRowProps): React.ReactElement {\n const t = useT()\n const rowBg = selected ? 'bg-brand-violet/10' : 'bg-card hover:bg-muted'\n const codeColor = selected ? 'text-brand-violet' : 'text-foreground'\n const amountColor = selected ? 'text-brand-violet font-semibold' : 'text-foreground font-normal'\n const currencyColor = selected ? 'text-brand-violet/80' : 'text-muted-foreground'\n const countColor = selected ? 'text-brand-violet font-semibold' : 'text-foreground/80 font-normal'\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n role=\"radio\"\n aria-checked={selected}\n onClick={onClick}\n className={`h-auto w-full justify-start gap-3 rounded-none px-6 py-2.5 text-left font-normal ${rowBg}`}\n >\n <RadioDot selected={selected} />\n <span className=\"flex items-center gap-1.5\">\n <span className={`text-sm leading-normal ${codeColor} ${selected ? 'font-semibold' : 'font-normal'}`}>\n {label}\n </span>\n {showBasePill ? (\n // text-[9px] is an intentional Figma exception for the BASE badge (node 1251:671).\n // The DS scale bottoms out at text-xs (12px); this 9px is design-system tracked as\n // a tiny-badge exception and should not be migrated to text-overline.\n <span className=\"inline-flex items-center rounded-sm bg-muted px-1.5 py-px text-[9px] font-bold uppercase leading-normal tracking-wider text-muted-foreground\">\n {translateWithFallback(t, 'customers.deals.kanban.currencyBreakdown.basePill', 'BASE')}\n </span>\n ) : null}\n </span>\n <span className=\"flex-1\" aria-hidden=\"true\" />\n <span className=\"flex items-baseline gap-1\">\n <span className={`text-sm leading-normal tabular-nums ${amountColor}`}>\n {formatAmount(amount)}\n </span>\n {!isAllCurrencies || amountCurrency ? (\n <span className={`text-xs leading-normal ${currencyColor}`}>{amountCurrency}</span>\n ) : null}\n </span>\n <span className={`pl-5 text-xs leading-normal tabular-nums ${countColor}`}>{count}</span>\n </Button>\n )\n}\n\nfunction RadioDot({ selected }: { selected: boolean }): React.ReactElement {\n return (\n <span\n className={`flex size-4 shrink-0 items-center justify-center rounded-full border ${\n selected ? 'border-brand-violet' : 'border-input bg-card'\n }`}\n aria-hidden=\"true\"\n >\n {selected ? <span className=\"size-2 rounded-full bg-brand-violet\" /> : null}\n </span>\n )\n}\n\nfunction formatAmount(amount: number): string {\n return new Intl.NumberFormat(undefined, {\n style: 'decimal',\n maximumFractionDigits: 0,\n useGrouping: true,\n }).format(Math.round(amount))\n}\n\nfunction formatToday(): string {\n return new Intl.DateTimeFormat(undefined, {\n year: 'numeric',\n month: 'short',\n day: '2-digit',\n }).format(new Date())\n}\n\nexport default CurrencyFilterPopover\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|