@open-mercato/core 0.6.3-develop.3709.1.0f15e09a5f → 0.6.3-develop.3753.1.29e9a20dde
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/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +2 -2
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js +1 -0
- package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js.map +2 -2
- package/dist/modules/messages/components/inboxFilters.js +4 -2
- package/dist/modules/messages/components/inboxFilters.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +4 -4
- package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.tsx +1 -1
- package/src/modules/messages/components/inboxFilters.ts +4 -2
- package/src/modules/messages/i18n/de.json +4 -2
- package/src/modules/messages/i18n/en.json +4 -2
- package/src/modules/messages/i18n/es.json +4 -2
- package/src/modules/messages/i18n/pl.json +4 -2
package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js
CHANGED
|
@@ -29,8 +29,8 @@ function normalizeFilters(context) {
|
|
|
29
29
|
}
|
|
30
30
|
function computeCatalogMerchandisingPageContext(context) {
|
|
31
31
|
const totalMatching = readNumber(context?.totalMatching ?? context?.total);
|
|
32
|
-
const selectedRowIds = Array.isArray(context?.
|
|
33
|
-
const selectedCount = selectedRowIds.length > 0 ? selectedRowIds.length : readNumber(context?.
|
|
32
|
+
const selectedRowIds = Array.isArray(context?.selectedRowIds) ? context.selectedRowIds : [];
|
|
33
|
+
const selectedCount = selectedRowIds.length > 0 ? selectedRowIds.length : readNumber(context?.selectedCount);
|
|
34
34
|
return {
|
|
35
35
|
view: "catalog.products.list",
|
|
36
36
|
entityType: "catalog.products.list",
|
package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\n/**\n * Step 5.15 \u2014 Catalog merchandising AiChat injection widget.\n *\n * Reuses the Step 4.9 `MerchandisingAssistantSheet` component but loads\n * it through the widget-injection system instead of being imported\n * directly from the products list page. `pageContext` follows spec \u00A710.1\n * exactly and is derived from the DataTable's injection context (filter\n * snapshot + total-matching count). Selection data is not exposed by the\n * shared DataTable today (Phase 2 contract); `selectedCount` ships as 0\n * until the host lifts selection into injection context.\n */\n\nimport * as React from 'react'\nimport MerchandisingAssistantSheet, {\n type MerchandisingPageContext,\n type MerchandisingPageContextFilter,\n} from '../../../backend/catalog/products/MerchandisingAssistantSheet'\n\ninterface HostInjectionContext {\n search?: string\n filters?: {\n categoryIds?: unknown\n tagIds?: unknown\n status?: unknown\n }\n customFieldset?: string | null\n page?: number\n sorting?: unknown\n scopeVersion?: unknown\n total?: number | string\n totalMatching?: number | string\n /** Selected row IDs from DataTable (auto-enriched when bulk actions are present). */\n
|
|
5
|
-
"mappings": ";AA2GS;AA7FT,YAAY,WAAW;AACvB,OAAO,iCAGA;AAwBP,SAAS,WAAW,OAA+B;AACjD,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACjE;AAEA,SAAS,WAAW,OAAwB;AAC1C,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,SAAS,OAAO,SAAS,OAAO,EAAE;AACxC,QAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,SAA2E;AACnG,QAAM,gBAAgB,SAAS,SAAS;AACxC,QAAM,cAAc,MAAM,QAAQ,aAAa,IAAI,gBAAgB,CAAC;AACpE,QAAM,kBAAkB,YACrB,IAAI,UAAU,EACd,KAAK,CAAC,UAA2B,UAAU,QAAQ,MAAM,SAAS,CAAC,KAAK;AAE3E,QAAM,UAAU,SAAS,SAAS;AAClC,QAAM,OAAO,MAAM,QAAQ,OAAO,IAC9B,QAAQ,IAAI,UAAU,EAAE,OAAO,CAAC,UAA2B,UAAU,IAAI,IACzE,CAAC;AAEL,QAAM,SAAS,WAAW,SAAS,SAAS,MAAM;AAElD,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AACF;AAMO,SAAS,uCACd,SAC0B;AAC1B,QAAM,gBAAgB,WAAW,SAAS,iBAAiB,SAAS,KAAK;AACzE,QAAM,iBAAiB,MAAM,QAAQ,SAAS,
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\n/**\n * Step 5.15 \u2014 Catalog merchandising AiChat injection widget.\n *\n * Reuses the Step 4.9 `MerchandisingAssistantSheet` component but loads\n * it through the widget-injection system instead of being imported\n * directly from the products list page. `pageContext` follows spec \u00A710.1\n * exactly and is derived from the DataTable's injection context (filter\n * snapshot + total-matching count). Selection data is not exposed by the\n * shared DataTable today (Phase 2 contract); `selectedCount` ships as 0\n * until the host lifts selection into injection context.\n */\n\nimport * as React from 'react'\nimport MerchandisingAssistantSheet, {\n type MerchandisingPageContext,\n type MerchandisingPageContextFilter,\n} from '../../../backend/catalog/products/MerchandisingAssistantSheet'\n\ninterface HostInjectionContext {\n search?: string\n filters?: {\n categoryIds?: unknown\n tagIds?: unknown\n status?: unknown\n }\n customFieldset?: string | null\n page?: number\n sorting?: unknown\n scopeVersion?: unknown\n total?: number | string\n totalMatching?: number | string\n /** Selected row IDs from DataTable (auto-enriched when bulk actions are present). */\n selectedRowIds?: string[]\n selectedCount?: number\n}\n\ninterface MerchandisingAssistantTriggerProps {\n context?: HostInjectionContext\n}\n\nfunction readString(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\nfunction readNumber(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const parsed = Number.parseInt(value, 10)\n if (Number.isFinite(parsed)) return parsed\n }\n return 0\n}\n\nfunction normalizeFilters(context: HostInjectionContext | undefined): MerchandisingPageContextFilter {\n const rawCategories = context?.filters?.categoryIds\n const categoryIds = Array.isArray(rawCategories) ? rawCategories : []\n const firstCategoryId = categoryIds\n .map(readString)\n .find((value): value is string => value !== null && value.length > 0) ?? null\n\n const rawTags = context?.filters?.tagIds\n const tags = Array.isArray(rawTags)\n ? rawTags.map(readString).filter((value): value is string => value !== null)\n : []\n\n const status = readString(context?.filters?.status)\n\n return {\n categoryId: firstCategoryId,\n priceRange: null,\n tags,\n status,\n }\n}\n\n/**\n * Exposed for unit tests so the page-context derivation is exercisable\n * without mounting the widget.\n */\nexport function computeCatalogMerchandisingPageContext(\n context: HostInjectionContext | undefined,\n): MerchandisingPageContext {\n const totalMatching = readNumber(context?.totalMatching ?? context?.total)\n const selectedRowIds = Array.isArray(context?.selectedRowIds) ? context.selectedRowIds : []\n const selectedCount = selectedRowIds.length > 0 ? selectedRowIds.length : readNumber(context?.selectedCount)\n return {\n view: 'catalog.products.list',\n entityType: 'catalog.products.list',\n recordType: null,\n recordId: selectedRowIds.join(','),\n extra: {\n filter: normalizeFilters(context),\n totalMatching,\n selectedCount,\n },\n }\n}\n\nexport default function MerchandisingAssistantTriggerWidget({\n context,\n}: MerchandisingAssistantTriggerProps) {\n const pageContext = React.useMemo(\n () => computeCatalogMerchandisingPageContext(context),\n [context],\n )\n return <MerchandisingAssistantSheet pageContext={pageContext} />\n}\n"],
|
|
5
|
+
"mappings": ";AA2GS;AA7FT,YAAY,WAAW;AACvB,OAAO,iCAGA;AAwBP,SAAS,WAAW,OAA+B;AACjD,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACjE;AAEA,SAAS,WAAW,OAAwB;AAC1C,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,SAAS,OAAO,SAAS,OAAO,EAAE;AACxC,QAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,SAA2E;AACnG,QAAM,gBAAgB,SAAS,SAAS;AACxC,QAAM,cAAc,MAAM,QAAQ,aAAa,IAAI,gBAAgB,CAAC;AACpE,QAAM,kBAAkB,YACrB,IAAI,UAAU,EACd,KAAK,CAAC,UAA2B,UAAU,QAAQ,MAAM,SAAS,CAAC,KAAK;AAE3E,QAAM,UAAU,SAAS,SAAS;AAClC,QAAM,OAAO,MAAM,QAAQ,OAAO,IAC9B,QAAQ,IAAI,UAAU,EAAE,OAAO,CAAC,UAA2B,UAAU,IAAI,IACzE,CAAC;AAEL,QAAM,SAAS,WAAW,SAAS,SAAS,MAAM;AAElD,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AACF;AAMO,SAAS,uCACd,SAC0B;AAC1B,QAAM,gBAAgB,WAAW,SAAS,iBAAiB,SAAS,KAAK;AACzE,QAAM,iBAAiB,MAAM,QAAQ,SAAS,cAAc,IAAI,QAAQ,iBAAiB,CAAC;AAC1F,QAAM,gBAAgB,eAAe,SAAS,IAAI,eAAe,SAAS,WAAW,SAAS,aAAa;AAC3G,SAAO;AAAA,IACL,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,UAAU,eAAe,KAAK,GAAG;AAAA,IACjC,OAAO;AAAA,MACL,QAAQ,iBAAiB,OAAO;AAAA,MAChC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEe,SAAR,oCAAqD;AAAA,EAC1D;AACF,GAAuC;AACrC,QAAM,cAAc,MAAM;AAAA,IACxB,MAAM,uCAAuC,OAAO;AAAA,IACpD,CAAC,OAAO;AAAA,EACV;AACA,SAAO,oBAAC,+BAA4B,aAA0B;AAChE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\n// See /framework/ai-assistant/agents \u2192 \"Deal Analyzer demo\" for the\n// pageContext contract (selectedRowIds \u2192 recordId comma-list) and the\n// loop primitives this widget surfaces.\n\nimport * as React from 'react'\nimport { Handshake, PanelRightOpen, TrendingDown } from 'lucide-react'\nimport { AiChat, type AiChatSuggestion, type AiChatContextItem } from '@open-mercato/ui/ai/AiChat'\nimport { AiIcon } from '@open-mercato/ui/ai/AiIcon'\nimport { useAiDock } from '@open-mercato/ui/ai/AiDock'\nimport { useAiChatSessions } from '@open-mercato/ui/ai/AiChatSessions'\nimport { ChatPaneTabs } from '@open-mercato/ui/ai/ChatPaneTabs'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport const DEAL_ANALYZER_AGENT_ID = 'customers.deal_analyzer'\n\nexport interface DealAnalyzerPageContext {\n view: 'customers.deals.list'\n recordType: null\n recordId: string | null\n extra: {\n selectedCount: number\n totalMatching: number\n }\n}\n\ninterface HostInjectionContext {\n tableId?: string | null\n selectedRowIds?: string[]\n selectedCount?: number\n total?: number\n totalMatching?: number\n rowCount?: number\n}\n\nfunction readNumber(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const parsed = Number.parseInt(value, 10)\n if (Number.isFinite(parsed)) return parsed\n }\n return 0\n}\n\nfunction readString(value: unknown): string {\n return typeof value === 'string' ? value : ''\n}\n\nfunction buildDealAnalyzerPageContext(context: HostInjectionContext | undefined): DealAnalyzerPageContext {\n const selectedIdsRaw = Array.isArray(context?.selectedRowIds) ? context?.selectedRowIds ?? [] : []\n const selectedIds = selectedIdsRaw.map(readString).filter((id) => id.length > 0)\n const selectedCount = selectedIds.length > 0\n ? selectedIds.length\n : readNumber(context?.selectedCount)\n const totalMatching = readNumber(context?.totalMatching ?? context?.total ?? context?.rowCount)\n const recordId = selectedIds.length > 0 ? selectedIds.join(',') : null\n return {\n view: 'customers.deals.list',\n recordType: null,\n recordId,\n extra: {\n selectedCount,\n totalMatching,\n },\n }\n}\n\nfunction useDealAnalyzerSuggestions(\n hasSelection: boolean,\n selectedCount: number,\n): AiChatSuggestion[] {\n const t = useT()\n return React.useMemo(() => {\n if (hasSelection) {\n return [\n {\n label: t(\n 'customers.deal_analyzer.suggestions.analyzeSelected',\n 'Analyze selected deals',\n ),\n prompt: `Analyze my ${selectedCount} selected deals and surface any that are stalled`,\n icon: <TrendingDown className=\"size-4\" />,\n },\n {\n label: t(\n 'customers.deal_analyzer.suggestions.proposeStageMove',\n 'Propose stage moves for selected deals',\n ),\n prompt: `Analyze my ${selectedCount} selected deals and propose stage moves for stalled high-value ones`,\n icon: <Handshake className=\"size-4\" />,\n },\n ]\n }\n return [\n {\n label: t(\n 'customers.deal_analyzer.suggestions.analyzeStalledDeals',\n 'Analyze stalled deals',\n ),\n prompt: 'Analyze stalled deals from the last 30 days and propose a stage move for the highest value one',\n icon: <TrendingDown className=\"size-4\" />,\n },\n {\n label: t(\n 'customers.deal_analyzer.suggestions.showAtRiskPipeline',\n 'Show at-risk pipeline',\n ),\n prompt: 'Show me deals with no activity in the last 14 days worth more than $5,000',\n icon: <Handshake className=\"size-4\" />,\n },\n {\n label: t(\n 'customers.deal_analyzer.suggestions.overviewByStage',\n 'Deal health overview',\n ),\n prompt: 'Give me a health overview of all open deals ranked by activity recency',\n icon: <AiIcon className=\"size-4\" />,\n },\n ]\n }, [hasSelection, selectedCount, t])\n}\n\nfunction useDealAnalyzerContextItems(pageContext: DealAnalyzerPageContext): AiChatContextItem[] {\n const t = useT()\n return React.useMemo(() => {\n const items: AiChatContextItem[] = []\n const { selectedCount, totalMatching } = pageContext.extra\n if (selectedCount > 0) {\n items.push({\n label: t(\n 'customers.deal_analyzer.context.selectedDeals',\n '{count} deals selected',\n ).replace('{count}', String(selectedCount)),\n })\n } else if (totalMatching > 0) {\n items.push({\n label: t(\n 'customers.deal_analyzer.context.dealsInView',\n '{count} deals in view',\n ).replace('{count}', String(totalMatching)),\n })\n }\n return items\n }, [pageContext, t])\n}\n\ninterface DealAnalyzerTriggerProps {\n context?: HostInjectionContext\n}\n\nexport default function DealAnalyzerTriggerWidget({ context }: DealAnalyzerTriggerProps) {\n const t = useT()\n const dock = useAiDock()\n const [open, setOpen] = React.useState(false)\n const pageContext = React.useMemo(() => buildDealAnalyzerPageContext(context), [context])\n\n const selectedCount = pageContext.extra.selectedCount\n const hasSelection = selectedCount > 0\n const suggestions = useDealAnalyzerSuggestions(hasSelection, selectedCount)\n const contextItems = useDealAnalyzerContextItems(pageContext)\n\n const handleTriggerClick = React.useCallback(() => {\n if (dock.state.assistant?.agent === DEAL_ANALYZER_AGENT_ID) {\n dock.dock(dock.state.assistant)\n setOpen(false)\n return\n }\n setOpen(true)\n }, [dock])\n\n const handleDock = React.useCallback(() => {\n dock.dock({\n agent: DEAL_ANALYZER_AGENT_ID,\n label: t('customers.deal_analyzer.sheet.title', 'Deal Analyzer'),\n description: t('customers.deal_analyzer.dock.subtitle', 'Deals'),\n pageContext: pageContext as unknown as Record<string, unknown>,\n placeholder: t(\n 'customers.deal_analyzer.sheet.composerPlaceholder',\n 'Analyze stalled deals, propose stage moves...',\n ),\n suggestions,\n contextItems,\n welcomeTitle: t('customers.deal_analyzer.sheet.welcomeTitle', 'Deal Analyzer'),\n welcomeDescription: hasSelection\n ? t(\n 'customers.deal_analyzer.sheet.welcomeDescriptionSelection',\n 'Ready to analyze your {count} selected deals:',\n ).replace('{count}', String(selectedCount))\n : t(\n 'customers.deal_analyzer.sheet.welcomeDescriptionAll',\n 'Analyze deal pipeline health and propose stage moves:',\n ),\n })\n setOpen(false)\n }, [contextItems, dock, hasSelection, pageContext, selectedCount, suggestions, t])\n\n const triggerLabel = t(\n 'customers.deal_analyzer.trigger.ariaLabel',\n 'Open Deal Analyzer AI agent',\n )\n const labelText = t('customers.ai_assistant.trigger.label', 'AI')\n\n return (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleTriggerClick}\n data-ai-deal-analyzer-trigger=\"\"\n aria-label={triggerLabel}\n title={triggerLabel}\n className={cn('relative', 'hover:bg-brand-violet/10')}\n >\n <AiIcon className=\"size-4\" />\n <span>{labelText}</span>\n {hasSelection ? (\n <span\n className=\"absolute -top-1 -right-1 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-medium leading-none text-primary-foreground\"\n data-ai-deal-analyzer-selected-count={selectedCount}\n >\n {selectedCount}\n </span>\n ) : null}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent\n className={cn(\n 'top-0 left-0 right-0 bottom-0 translate-x-0 translate-y-0 max-w-none w-screen h-svh max-h-svh rounded-none',\n 'sm:top-0 sm:bottom-0 sm:right-0 sm:left-auto sm:translate-x-0 sm:translate-y-0',\n 'sm:max-w-xl sm:w-[36rem] sm:rounded-l-2xl sm:h-screen sm:max-h-screen',\n 'flex flex-col gap-3 p-4',\n )}\n data-ai-deal-analyzer-sheet=\"\"\n >\n <DialogHeader>\n <div className=\"flex items-center gap-3 pr-8\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('customers.deal_analyzer.sheet.dock', 'Dock to side')}\n title={t('customers.deal_analyzer.sheet.dock', 'Dock to side')}\n onClick={handleDock}\n data-ai-deal-analyzer-dock=\"\"\n className=\"hidden lg:inline-flex shrink-0\"\n >\n <PanelRightOpen className=\"size-4\" aria-hidden />\n </IconButton>\n <DialogTitle className=\"flex-1 min-w-0 truncate\">\n {t('customers.deal_analyzer.sheet.title', 'Deal Analyzer')}\n </DialogTitle>\n {hasSelection ? (\n <span\n className=\"shrink-0 inline-flex items-center rounded-full border border-border bg-secondary px-2 py-0.5 text-xs text-secondary-foreground\"\n data-ai-deal-analyzer-selection-pill=\"\"\n data-ai-deal-analyzer-selected-count={selectedCount}\n >\n {t(\n 'customers.deal_analyzer.sheet.selectionPill',\n 'Acting on {count} deals',\n ).replace('{count}', String(selectedCount))}\n </span>\n ) : null}\n </div>\n <DialogDescription>\n {hasSelection\n ? t(\n 'customers.deal_analyzer.sheet.descriptionWithSelection',\n 'Analyzing {count} selected deals for pipeline health and stage move proposals.',\n ).replace('{count}', String(selectedCount))\n : t(\n 'customers.deal_analyzer.sheet.description',\n 'Multi-step deal health analyzer. Surfaces stalled deals and proposes stage transitions for approval.',\n )}\n </DialogDescription>\n </DialogHeader>\n <DealAnalyzerChatBody\n pageContext={pageContext}\n suggestions={suggestions}\n contextItems={contextItems}\n hasSelection={hasSelection}\n selectedCount={selectedCount}\n />\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\ninterface DealAnalyzerChatBodyProps {\n pageContext: DealAnalyzerPageContext\n suggestions: AiChatSuggestion[]\n contextItems: AiChatContextItem[]\n hasSelection: boolean\n selectedCount: number\n}\n\nfunction DealAnalyzerChatBody({\n pageContext,\n suggestions,\n contextItems,\n hasSelection,\n selectedCount,\n}: DealAnalyzerChatBodyProps) {\n const t = useT()\n const sessions = useAiChatSessions()\n const session = sessions.getActiveSession(DEAL_ANALYZER_AGENT_ID)\n\n React.useEffect(() => {\n if (!session) sessions.ensureSession(DEAL_ANALYZER_AGENT_ID)\n }, [session, sessions])\n\n return (\n <>\n <ChatPaneTabs agentId={DEAL_ANALYZER_AGENT_ID} className=\"border-b\" />\n <div className=\"min-h-0 flex-1\" data-ai-deal-analyzer-chat-container=\"\">\n {session ? (\n <AiChat\n key={session.id}\n agent={DEAL_ANALYZER_AGENT_ID}\n conversationId={session.conversationId}\n pageContext={pageContext as unknown as Record<string, unknown>}\n className=\"h-full\"\n placeholder={t(\n 'customers.deal_analyzer.sheet.composerPlaceholder',\n 'Analyze stalled deals, propose stage moves...',\n )}\n suggestions={suggestions}\n contextItems={contextItems}\n welcomeTitle={t('customers.deal_analyzer.sheet.welcomeTitle', 'Deal Analyzer')}\n welcomeDescription={\n hasSelection\n ? t(\n 'customers.deal_analyzer.sheet.welcomeDescriptionSelection',\n 'Ready to analyze your {count} selected deals:',\n ).replace('{count}', String(selectedCount))\n : t(\n 'customers.deal_analyzer.sheet.welcomeDescriptionAll',\n 'Analyze deal pipeline health and propose stage moves:',\n )\n }\n />\n ) : null}\n </div>\n </>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AA4FgB,SA0HZ,UA1HY,KA2HV,YA3HU;AAtFhB,YAAY,WAAW;AACvB,SAAS,WAAW,gBAAgB,oBAAoB;AACxD,SAAS,cAA6D;AACtE,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAC1B,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAC7B,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,YAAY;AACrB,SAAS,UAAU;AAEZ,MAAM,yBAAyB;AAqBtC,SAAS,WAAW,OAAwB;AAC1C,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,SAAS,OAAO,SAAS,OAAO,EAAE;AACxC,QAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAEA,SAAS,WAAW,OAAwB;AAC1C,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\n// See /framework/ai-assistant/agents \u2192 \"Deal Analyzer demo\" for the\n// pageContext contract (selectedRowIds \u2192 recordId comma-list) and the\n// loop primitives this widget surfaces.\n\nimport * as React from 'react'\nimport { Handshake, PanelRightOpen, TrendingDown } from 'lucide-react'\nimport { AiChat, type AiChatSuggestion, type AiChatContextItem } from '@open-mercato/ui/ai/AiChat'\nimport { AiIcon } from '@open-mercato/ui/ai/AiIcon'\nimport { useAiDock } from '@open-mercato/ui/ai/AiDock'\nimport { useAiChatSessions } from '@open-mercato/ui/ai/AiChatSessions'\nimport { ChatPaneTabs } from '@open-mercato/ui/ai/ChatPaneTabs'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport const DEAL_ANALYZER_AGENT_ID = 'customers.deal_analyzer'\n\nexport interface DealAnalyzerPageContext {\n view: 'customers.deals.list'\n recordType: null\n recordId: string | null\n extra: {\n selectedCount: number\n totalMatching: number\n }\n}\n\ninterface HostInjectionContext {\n tableId?: string | null\n selectedRowIds?: string[]\n selectedCount?: number\n total?: number\n totalMatching?: number\n rowCount?: number\n}\n\nfunction readNumber(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const parsed = Number.parseInt(value, 10)\n if (Number.isFinite(parsed)) return parsed\n }\n return 0\n}\n\nfunction readString(value: unknown): string {\n return typeof value === 'string' ? value : ''\n}\n\nexport function buildDealAnalyzerPageContext(context: HostInjectionContext | undefined): DealAnalyzerPageContext {\n const selectedIdsRaw = Array.isArray(context?.selectedRowIds) ? context?.selectedRowIds ?? [] : []\n const selectedIds = selectedIdsRaw.map(readString).filter((id) => id.length > 0)\n const selectedCount = selectedIds.length > 0\n ? selectedIds.length\n : readNumber(context?.selectedCount)\n const totalMatching = readNumber(context?.totalMatching ?? context?.total ?? context?.rowCount)\n const recordId = selectedIds.length > 0 ? selectedIds.join(',') : null\n return {\n view: 'customers.deals.list',\n recordType: null,\n recordId,\n extra: {\n selectedCount,\n totalMatching,\n },\n }\n}\n\nfunction useDealAnalyzerSuggestions(\n hasSelection: boolean,\n selectedCount: number,\n): AiChatSuggestion[] {\n const t = useT()\n return React.useMemo(() => {\n if (hasSelection) {\n return [\n {\n label: t(\n 'customers.deal_analyzer.suggestions.analyzeSelected',\n 'Analyze selected deals',\n ),\n prompt: `Analyze my ${selectedCount} selected deals and surface any that are stalled`,\n icon: <TrendingDown className=\"size-4\" />,\n },\n {\n label: t(\n 'customers.deal_analyzer.suggestions.proposeStageMove',\n 'Propose stage moves for selected deals',\n ),\n prompt: `Analyze my ${selectedCount} selected deals and propose stage moves for stalled high-value ones`,\n icon: <Handshake className=\"size-4\" />,\n },\n ]\n }\n return [\n {\n label: t(\n 'customers.deal_analyzer.suggestions.analyzeStalledDeals',\n 'Analyze stalled deals',\n ),\n prompt: 'Analyze stalled deals from the last 30 days and propose a stage move for the highest value one',\n icon: <TrendingDown className=\"size-4\" />,\n },\n {\n label: t(\n 'customers.deal_analyzer.suggestions.showAtRiskPipeline',\n 'Show at-risk pipeline',\n ),\n prompt: 'Show me deals with no activity in the last 14 days worth more than $5,000',\n icon: <Handshake className=\"size-4\" />,\n },\n {\n label: t(\n 'customers.deal_analyzer.suggestions.overviewByStage',\n 'Deal health overview',\n ),\n prompt: 'Give me a health overview of all open deals ranked by activity recency',\n icon: <AiIcon className=\"size-4\" />,\n },\n ]\n }, [hasSelection, selectedCount, t])\n}\n\nfunction useDealAnalyzerContextItems(pageContext: DealAnalyzerPageContext): AiChatContextItem[] {\n const t = useT()\n return React.useMemo(() => {\n const items: AiChatContextItem[] = []\n const { selectedCount, totalMatching } = pageContext.extra\n if (selectedCount > 0) {\n items.push({\n label: t(\n 'customers.deal_analyzer.context.selectedDeals',\n '{count} deals selected',\n ).replace('{count}', String(selectedCount)),\n })\n } else if (totalMatching > 0) {\n items.push({\n label: t(\n 'customers.deal_analyzer.context.dealsInView',\n '{count} deals in view',\n ).replace('{count}', String(totalMatching)),\n })\n }\n return items\n }, [pageContext, t])\n}\n\ninterface DealAnalyzerTriggerProps {\n context?: HostInjectionContext\n}\n\nexport default function DealAnalyzerTriggerWidget({ context }: DealAnalyzerTriggerProps) {\n const t = useT()\n const dock = useAiDock()\n const [open, setOpen] = React.useState(false)\n const pageContext = React.useMemo(() => buildDealAnalyzerPageContext(context), [context])\n\n const selectedCount = pageContext.extra.selectedCount\n const hasSelection = selectedCount > 0\n const suggestions = useDealAnalyzerSuggestions(hasSelection, selectedCount)\n const contextItems = useDealAnalyzerContextItems(pageContext)\n\n const handleTriggerClick = React.useCallback(() => {\n if (dock.state.assistant?.agent === DEAL_ANALYZER_AGENT_ID) {\n dock.dock(dock.state.assistant)\n setOpen(false)\n return\n }\n setOpen(true)\n }, [dock])\n\n const handleDock = React.useCallback(() => {\n dock.dock({\n agent: DEAL_ANALYZER_AGENT_ID,\n label: t('customers.deal_analyzer.sheet.title', 'Deal Analyzer'),\n description: t('customers.deal_analyzer.dock.subtitle', 'Deals'),\n pageContext: pageContext as unknown as Record<string, unknown>,\n placeholder: t(\n 'customers.deal_analyzer.sheet.composerPlaceholder',\n 'Analyze stalled deals, propose stage moves...',\n ),\n suggestions,\n contextItems,\n welcomeTitle: t('customers.deal_analyzer.sheet.welcomeTitle', 'Deal Analyzer'),\n welcomeDescription: hasSelection\n ? t(\n 'customers.deal_analyzer.sheet.welcomeDescriptionSelection',\n 'Ready to analyze your {count} selected deals:',\n ).replace('{count}', String(selectedCount))\n : t(\n 'customers.deal_analyzer.sheet.welcomeDescriptionAll',\n 'Analyze deal pipeline health and propose stage moves:',\n ),\n })\n setOpen(false)\n }, [contextItems, dock, hasSelection, pageContext, selectedCount, suggestions, t])\n\n const triggerLabel = t(\n 'customers.deal_analyzer.trigger.ariaLabel',\n 'Open Deal Analyzer AI agent',\n )\n const labelText = t('customers.ai_assistant.trigger.label', 'AI')\n\n return (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleTriggerClick}\n data-ai-deal-analyzer-trigger=\"\"\n aria-label={triggerLabel}\n title={triggerLabel}\n className={cn('relative', 'hover:bg-brand-violet/10')}\n >\n <AiIcon className=\"size-4\" />\n <span>{labelText}</span>\n {hasSelection ? (\n <span\n className=\"absolute -top-1 -right-1 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-medium leading-none text-primary-foreground\"\n data-ai-deal-analyzer-selected-count={selectedCount}\n >\n {selectedCount}\n </span>\n ) : null}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent\n className={cn(\n 'top-0 left-0 right-0 bottom-0 translate-x-0 translate-y-0 max-w-none w-screen h-svh max-h-svh rounded-none',\n 'sm:top-0 sm:bottom-0 sm:right-0 sm:left-auto sm:translate-x-0 sm:translate-y-0',\n 'sm:max-w-xl sm:w-[36rem] sm:rounded-l-2xl sm:h-screen sm:max-h-screen',\n 'flex flex-col gap-3 p-4',\n )}\n data-ai-deal-analyzer-sheet=\"\"\n >\n <DialogHeader>\n <div className=\"flex items-center gap-3 pr-8\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('customers.deal_analyzer.sheet.dock', 'Dock to side')}\n title={t('customers.deal_analyzer.sheet.dock', 'Dock to side')}\n onClick={handleDock}\n data-ai-deal-analyzer-dock=\"\"\n className=\"hidden lg:inline-flex shrink-0\"\n >\n <PanelRightOpen className=\"size-4\" aria-hidden />\n </IconButton>\n <DialogTitle className=\"flex-1 min-w-0 truncate\">\n {t('customers.deal_analyzer.sheet.title', 'Deal Analyzer')}\n </DialogTitle>\n {hasSelection ? (\n <span\n className=\"shrink-0 inline-flex items-center rounded-full border border-border bg-secondary px-2 py-0.5 text-xs text-secondary-foreground\"\n data-ai-deal-analyzer-selection-pill=\"\"\n data-ai-deal-analyzer-selected-count={selectedCount}\n >\n {t(\n 'customers.deal_analyzer.sheet.selectionPill',\n 'Acting on {count} deals',\n ).replace('{count}', String(selectedCount))}\n </span>\n ) : null}\n </div>\n <DialogDescription>\n {hasSelection\n ? t(\n 'customers.deal_analyzer.sheet.descriptionWithSelection',\n 'Analyzing {count} selected deals for pipeline health and stage move proposals.',\n ).replace('{count}', String(selectedCount))\n : t(\n 'customers.deal_analyzer.sheet.description',\n 'Multi-step deal health analyzer. Surfaces stalled deals and proposes stage transitions for approval.',\n )}\n </DialogDescription>\n </DialogHeader>\n <DealAnalyzerChatBody\n pageContext={pageContext}\n suggestions={suggestions}\n contextItems={contextItems}\n hasSelection={hasSelection}\n selectedCount={selectedCount}\n />\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\ninterface DealAnalyzerChatBodyProps {\n pageContext: DealAnalyzerPageContext\n suggestions: AiChatSuggestion[]\n contextItems: AiChatContextItem[]\n hasSelection: boolean\n selectedCount: number\n}\n\nfunction DealAnalyzerChatBody({\n pageContext,\n suggestions,\n contextItems,\n hasSelection,\n selectedCount,\n}: DealAnalyzerChatBodyProps) {\n const t = useT()\n const sessions = useAiChatSessions()\n const session = sessions.getActiveSession(DEAL_ANALYZER_AGENT_ID)\n\n React.useEffect(() => {\n if (!session) sessions.ensureSession(DEAL_ANALYZER_AGENT_ID)\n }, [session, sessions])\n\n return (\n <>\n <ChatPaneTabs agentId={DEAL_ANALYZER_AGENT_ID} className=\"border-b\" />\n <div className=\"min-h-0 flex-1\" data-ai-deal-analyzer-chat-container=\"\">\n {session ? (\n <AiChat\n key={session.id}\n agent={DEAL_ANALYZER_AGENT_ID}\n conversationId={session.conversationId}\n pageContext={pageContext as unknown as Record<string, unknown>}\n className=\"h-full\"\n placeholder={t(\n 'customers.deal_analyzer.sheet.composerPlaceholder',\n 'Analyze stalled deals, propose stage moves...',\n )}\n suggestions={suggestions}\n contextItems={contextItems}\n welcomeTitle={t('customers.deal_analyzer.sheet.welcomeTitle', 'Deal Analyzer')}\n welcomeDescription={\n hasSelection\n ? t(\n 'customers.deal_analyzer.sheet.welcomeDescriptionSelection',\n 'Ready to analyze your {count} selected deals:',\n ).replace('{count}', String(selectedCount))\n : t(\n 'customers.deal_analyzer.sheet.welcomeDescriptionAll',\n 'Analyze deal pipeline health and propose stage moves:',\n )\n }\n />\n ) : null}\n </div>\n </>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA4FgB,SA0HZ,UA1HY,KA2HV,YA3HU;AAtFhB,YAAY,WAAW;AACvB,SAAS,WAAW,gBAAgB,oBAAoB;AACxD,SAAS,cAA6D;AACtE,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAC1B,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAC7B,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,YAAY;AACrB,SAAS,UAAU;AAEZ,MAAM,yBAAyB;AAqBtC,SAAS,WAAW,OAAwB;AAC1C,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,SAAS,OAAO,SAAS,OAAO,EAAE;AACxC,QAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAEA,SAAS,WAAW,OAAwB;AAC1C,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEO,SAAS,6BAA6B,SAAoE;AAC/G,QAAM,iBAAiB,MAAM,QAAQ,SAAS,cAAc,IAAI,SAAS,kBAAkB,CAAC,IAAI,CAAC;AACjG,QAAM,cAAc,eAAe,IAAI,UAAU,EAAE,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;AAC/E,QAAM,gBAAgB,YAAY,SAAS,IACvC,YAAY,SACZ,WAAW,SAAS,aAAa;AACrC,QAAM,gBAAgB,WAAW,SAAS,iBAAiB,SAAS,SAAS,SAAS,QAAQ;AAC9F,QAAM,WAAW,YAAY,SAAS,IAAI,YAAY,KAAK,GAAG,IAAI;AAClE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,YAAY;AAAA,IACZ;AAAA,IACA,OAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,2BACP,cACA,eACoB;AACpB,QAAM,IAAI,KAAK;AACf,SAAO,MAAM,QAAQ,MAAM;AACzB,QAAI,cAAc;AAChB,aAAO;AAAA,QACL;AAAA,UACE,OAAO;AAAA,YACL;AAAA,YACA;AAAA,UACF;AAAA,UACA,QAAQ,cAAc,aAAa;AAAA,UACnC,MAAM,oBAAC,gBAAa,WAAU,UAAS;AAAA,QACzC;AAAA,QACA;AAAA,UACE,OAAO;AAAA,YACL;AAAA,YACA;AAAA,UACF;AAAA,UACA,QAAQ,cAAc,aAAa;AAAA,UACnC,MAAM,oBAAC,aAAU,WAAU,UAAS;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,QACE,OAAO;AAAA,UACL;AAAA,UACA;AAAA,QACF;AAAA,QACA,QAAQ;AAAA,QACR,MAAM,oBAAC,gBAAa,WAAU,UAAS;AAAA,MACzC;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL;AAAA,UACA;AAAA,QACF;AAAA,QACA,QAAQ;AAAA,QACR,MAAM,oBAAC,aAAU,WAAU,UAAS;AAAA,MACtC;AAAA,MACA;AAAA,QACE,OAAO;AAAA,UACL;AAAA,UACA;AAAA,QACF;AAAA,QACA,QAAQ;AAAA,QACR,MAAM,oBAAC,UAAO,WAAU,UAAS;AAAA,MACnC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,cAAc,eAAe,CAAC,CAAC;AACrC;AAEA,SAAS,4BAA4B,aAA2D;AAC9F,QAAM,IAAI,KAAK;AACf,SAAO,MAAM,QAAQ,MAAM;AACzB,UAAM,QAA6B,CAAC;AACpC,UAAM,EAAE,eAAe,cAAc,IAAI,YAAY;AACrD,QAAI,gBAAgB,GAAG;AACrB,YAAM,KAAK;AAAA,QACT,OAAO;AAAA,UACL;AAAA,UACA;AAAA,QACF,EAAE,QAAQ,WAAW,OAAO,aAAa,CAAC;AAAA,MAC5C,CAAC;AAAA,IACH,WAAW,gBAAgB,GAAG;AAC5B,YAAM,KAAK;AAAA,QACT,OAAO;AAAA,UACL;AAAA,UACA;AAAA,QACF,EAAE,QAAQ,WAAW,OAAO,aAAa,CAAC;AAAA,MAC5C,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT,GAAG,CAAC,aAAa,CAAC,CAAC;AACrB;AAMe,SAAR,0BAA2C,EAAE,QAAQ,GAA6B;AACvF,QAAM,IAAI,KAAK;AACf,QAAM,OAAO,UAAU;AACvB,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,KAAK;AAC5C,QAAM,cAAc,MAAM,QAAQ,MAAM,6BAA6B,OAAO,GAAG,CAAC,OAAO,CAAC;AAExF,QAAM,gBAAgB,YAAY,MAAM;AACxC,QAAM,eAAe,gBAAgB;AACrC,QAAM,cAAc,2BAA2B,cAAc,aAAa;AAC1E,QAAM,eAAe,4BAA4B,WAAW;AAE5D,QAAM,qBAAqB,MAAM,YAAY,MAAM;AACjD,QAAI,KAAK,MAAM,WAAW,UAAU,wBAAwB;AAC1D,WAAK,KAAK,KAAK,MAAM,SAAS;AAC9B,cAAQ,KAAK;AACb;AAAA,IACF;AACA,YAAQ,IAAI;AAAA,EACd,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,aAAa,MAAM,YAAY,MAAM;AACzC,SAAK,KAAK;AAAA,MACR,OAAO;AAAA,MACP,OAAO,EAAE,uCAAuC,eAAe;AAAA,MAC/D,aAAa,EAAE,yCAAyC,OAAO;AAAA,MAC/D;AAAA,MACA,aAAa;AAAA,QACX;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc,EAAE,8CAA8C,eAAe;AAAA,MAC7E,oBAAoB,eAChB;AAAA,QACE;AAAA,QACA;AAAA,MACF,EAAE,QAAQ,WAAW,OAAO,aAAa,CAAC,IAC1C;AAAA,QACE;AAAA,QACA;AAAA,MACF;AAAA,IACN,CAAC;AACD,YAAQ,KAAK;AAAA,EACf,GAAG,CAAC,cAAc,MAAM,cAAc,aAAa,eAAe,aAAa,CAAC,CAAC;AAEjF,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,EACF;AACA,QAAM,YAAY,EAAE,wCAAwC,IAAI;AAEhE,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,SAAS;AAAA,QACT,iCAA8B;AAAA,QAC9B,cAAY;AAAA,QACZ,OAAO;AAAA,QACP,WAAW,GAAG,YAAY,0BAA0B;AAAA,QAEpD;AAAA,8BAAC,UAAO,WAAU,UAAS;AAAA,UAC3B,oBAAC,UAAM,qBAAU;AAAA,UAChB,eACC;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,wCAAsC;AAAA,cAErC;AAAA;AAAA,UACH,IACE;AAAA;AAAA;AAAA,IACN;AAAA,IACA,oBAAC,UAAO,MAAY,cAAc,SAChC;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACA,+BAA4B;AAAA,QAE5B;AAAA,+BAAC,gBACC;AAAA,iCAAC,SAAI,WAAU,gCACb;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,cAAY,EAAE,sCAAsC,cAAc;AAAA,kBAClE,OAAO,EAAE,sCAAsC,cAAc;AAAA,kBAC7D,SAAS;AAAA,kBACT,8BAA2B;AAAA,kBAC3B,WAAU;AAAA,kBAEV,8BAAC,kBAAe,WAAU,UAAS,eAAW,MAAC;AAAA;AAAA,cACjD;AAAA,cACA,oBAAC,eAAY,WAAU,2BACpB,YAAE,uCAAuC,eAAe,GAC3D;AAAA,cACC,eACC;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,wCAAqC;AAAA,kBACrC,wCAAsC;AAAA,kBAErC;AAAA,oBACC;AAAA,oBACA;AAAA,kBACF,EAAE,QAAQ,WAAW,OAAO,aAAa,CAAC;AAAA;AAAA,cAC5C,IACE;AAAA,eACN;AAAA,YACA,oBAAC,qBACE,yBACG;AAAA,cACE;AAAA,cACA;AAAA,YACF,EAAE,QAAQ,WAAW,OAAO,aAAa,CAAC,IAC1C;AAAA,cACE;AAAA,cACA;AAAA,YACF,GACN;AAAA,aACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA;AAAA,UACF;AAAA;AAAA;AAAA,IACF,GACF;AAAA,KACF;AAEJ;AAUA,SAAS,qBAAqB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA8B;AAC5B,QAAM,IAAI,KAAK;AACf,QAAM,WAAW,kBAAkB;AACnC,QAAM,UAAU,SAAS,iBAAiB,sBAAsB;AAEhE,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,QAAS,UAAS,cAAc,sBAAsB;AAAA,EAC7D,GAAG,CAAC,SAAS,QAAQ,CAAC;AAEtB,SACE,iCACE;AAAA,wBAAC,gBAAa,SAAS,wBAAwB,WAAU,YAAW;AAAA,IACpE,oBAAC,SAAI,WAAU,kBAAiB,wCAAqC,IAClE,oBACC;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO;AAAA,QACP,gBAAgB,QAAQ;AAAA,QACxB;AAAA,QACA,WAAU;AAAA,QACV,aAAa;AAAA,UACX;AAAA,UACA;AAAA,QACF;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc,EAAE,8CAA8C,eAAe;AAAA,QAC7E,oBACE,eACI;AAAA,UACE;AAAA,UACA;AAAA,QACF,EAAE,QAAQ,WAAW,OAAO,aAAa,CAAC,IAC1C;AAAA,UACE;AAAA,UACA;AAAA,QACF;AAAA;AAAA,MArBD,QAAQ;AAAA,IAuBf,IACE,MACN;AAAA,KACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -28,7 +28,8 @@ function buildMessagesInboxFilters({
|
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
id: "hasObjects",
|
|
31
|
-
label: t("messages.filters.hasObjects", "Has
|
|
31
|
+
label: t("messages.filters.hasObjects", "Has related records"),
|
|
32
|
+
tooltip: t("messages.filters.hasObjectsTooltip", "Shows messages that have Open Mercato records attached \u2014 such as orders, quotes, or customers."),
|
|
32
33
|
type: "select",
|
|
33
34
|
options: [
|
|
34
35
|
{ value: "", label: t("messages.filters.all", "All") },
|
|
@@ -48,7 +49,8 @@ function buildMessagesInboxFilters({
|
|
|
48
49
|
},
|
|
49
50
|
{
|
|
50
51
|
id: "hasActions",
|
|
51
|
-
label: t("messages.filters.hasActions", "Has
|
|
52
|
+
label: t("messages.filters.hasActions", "Has action requests"),
|
|
53
|
+
tooltip: t("messages.filters.hasActionsTooltip", "Shows messages where one or more attached records require a response (approval, rejection, or review)."),
|
|
52
54
|
type: "select",
|
|
53
55
|
options: [
|
|
54
56
|
{ value: "", label: t("messages.filters.all", "All") },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/components/inboxFilters.ts"],
|
|
4
|
-
"sourcesContent": ["import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\n\ntype Translator = (key: string, fallback?: string) => string\n\nexport type SenderOption = {\n value: string\n label: string\n description?: string | null\n}\n\nexport type MessagesInboxFilterContext = {\n t: Translator\n typeOptions: { value: string; label: string }[]\n senderOptions: SenderOption[]\n loadSenderOptions: (query?: string) => Promise<SenderOption[]>\n}\n\nexport function buildMessagesInboxFilters({\n t,\n typeOptions,\n senderOptions,\n loadSenderOptions,\n}: MessagesInboxFilterContext): FilterDef[] {\n const senderLabel = (val: string): string => {\n const match = senderOptions.find((opt) => opt.value === val)\n return match?.label ?? val\n }\n\n return [\n {\n id: 'status',\n label: t('messages.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'unread', label: t('messages.status.unread', 'Unread') },\n { value: 'read', label: t('messages.status.read', 'Read') },\n { value: 'archived', label: t('messages.status.archived', 'Archived') },\n ],\n },\n {\n id: 'type',\n label: t('messages.filters.type', 'Type'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],\n },\n {\n id: 'hasObjects',\n label: t('messages.filters.hasObjects', 'Has
|
|
5
|
-
"mappings": "AAiBO,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4C;AAC1C,QAAM,cAAc,CAAC,QAAwB;AAC3C,UAAM,QAAQ,cAAc,KAAK,CAAC,QAAQ,IAAI,UAAU,GAAG;AAC3D,WAAO,OAAO,SAAS;AAAA,EACzB;AAEA,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,2BAA2B,QAAQ;AAAA,MAC5C,MAAM;AAAA,MACN,SAAS;AAAA,QACP,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE;AAAA,QACrD,EAAE,OAAO,UAAU,OAAO,EAAE,0BAA0B,QAAQ,EAAE;AAAA,QAChE,EAAE,OAAO,QAAQ,OAAO,EAAE,wBAAwB,MAAM,EAAE;AAAA,QAC1D,EAAE,OAAO,YAAY,OAAO,EAAE,4BAA4B,UAAU,EAAE;AAAA,MACxE;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,yBAAyB,MAAM;AAAA,MACxC,MAAM;AAAA,MACN,SAAS,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,wBAAwB,KAAK,EAAE,GAAG,GAAG,WAAW;AAAA,IAClF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,+BAA+B,
|
|
4
|
+
"sourcesContent": ["import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\n\ntype Translator = (key: string, fallback?: string) => string\n\nexport type SenderOption = {\n value: string\n label: string\n description?: string | null\n}\n\nexport type MessagesInboxFilterContext = {\n t: Translator\n typeOptions: { value: string; label: string }[]\n senderOptions: SenderOption[]\n loadSenderOptions: (query?: string) => Promise<SenderOption[]>\n}\n\nexport function buildMessagesInboxFilters({\n t,\n typeOptions,\n senderOptions,\n loadSenderOptions,\n}: MessagesInboxFilterContext): FilterDef[] {\n const senderLabel = (val: string): string => {\n const match = senderOptions.find((opt) => opt.value === val)\n return match?.label ?? val\n }\n\n return [\n {\n id: 'status',\n label: t('messages.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'unread', label: t('messages.status.unread', 'Unread') },\n { value: 'read', label: t('messages.status.read', 'Read') },\n { value: 'archived', label: t('messages.status.archived', 'Archived') },\n ],\n },\n {\n id: 'type',\n label: t('messages.filters.type', 'Type'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],\n },\n {\n id: 'hasObjects',\n label: t('messages.filters.hasObjects', 'Has related records'),\n tooltip: t('messages.filters.hasObjectsTooltip', 'Shows messages that have Open Mercato records attached \u2014 such as orders, quotes, or customers.'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasAttachments',\n label: t('messages.filters.hasAttachments', 'Has attachments'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasActions',\n label: t('messages.filters.hasActions', 'Has action requests'),\n tooltip: t('messages.filters.hasActionsTooltip', 'Shows messages where one or more attached records require a response (approval, rejection, or review).'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'senderId',\n label: t('messages.filters.sender', 'Sender'),\n type: 'combobox',\n placeholder: t('messages.filters.senderEmpty', 'Type to search users'),\n options: senderOptions,\n loadOptions: loadSenderOptions,\n formatValue: senderLabel,\n formatDescription: (val: string) =>\n senderOptions.find((opt) => opt.value === val)?.description ?? null,\n },\n {\n id: 'since',\n label: t('messages.filters.since', 'Sent after'),\n type: 'text',\n placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DD'),\n },\n ]\n}\n\nconst DATE_ONLY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/\n\nexport function normalizeMessagesSinceValue(input: string): string | null {\n const trimmed = input.trim()\n if (!trimmed) return null\n if (DATE_ONLY_PATTERN.test(trimmed)) {\n const parsed = new Date(`${trimmed}T00:00:00.000Z`)\n if (Number.isNaN(parsed.getTime())) return null\n if (parsed.toISOString().slice(0, 10) !== trimmed) return null\n return parsed.toISOString()\n }\n const parsed = new Date(trimmed)\n if (Number.isNaN(parsed.getTime())) return null\n return parsed.toISOString()\n}\n\nexport type BuildMessagesListParamsOptions = {\n folder: string\n page: number\n pageSize: number\n search: string\n filterValues: FilterValues\n}\n\nexport function buildMessagesListParams({\n folder,\n page,\n pageSize,\n search,\n filterValues,\n}: BuildMessagesListParamsOptions): URLSearchParams {\n const params = new URLSearchParams()\n params.set('folder', folder)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n\n if (search.trim()) {\n params.set('search', search.trim())\n }\n\n const stringValue = (id: string): string => {\n const raw = filterValues[id]\n return typeof raw === 'string' ? raw.trim() : ''\n }\n\n const status = stringValue('status')\n const type = stringValue('type')\n const hasObjects = stringValue('hasObjects')\n const hasAttachments = stringValue('hasAttachments')\n const hasActions = stringValue('hasActions')\n const senderId = stringValue('senderId')\n const sinceRaw = stringValue('since')\n\n if (status) params.set('status', status)\n if (type) params.set('type', type)\n if (hasObjects) params.set('hasObjects', hasObjects)\n if (hasAttachments) params.set('hasAttachments', hasAttachments)\n if (hasActions) params.set('hasActions', hasActions)\n if (senderId) params.set('senderId', senderId)\n if (sinceRaw) {\n const normalized = normalizeMessagesSinceValue(sinceRaw)\n if (normalized) params.set('since', normalized)\n }\n\n return params\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["parsed"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.3-develop.
|
|
3
|
+
"version": "0.6.3-develop.3753.1.29e9a20dde",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -242,16 +242,16 @@
|
|
|
242
242
|
"zod": "^4.4.3"
|
|
243
243
|
},
|
|
244
244
|
"peerDependencies": {
|
|
245
|
-
"@open-mercato/ai-assistant": "0.6.3-develop.
|
|
246
|
-
"@open-mercato/shared": "0.6.3-develop.
|
|
247
|
-
"@open-mercato/ui": "0.6.3-develop.
|
|
245
|
+
"@open-mercato/ai-assistant": "0.6.3-develop.3753.1.29e9a20dde",
|
|
246
|
+
"@open-mercato/shared": "0.6.3-develop.3753.1.29e9a20dde",
|
|
247
|
+
"@open-mercato/ui": "0.6.3-develop.3753.1.29e9a20dde",
|
|
248
248
|
"react": "^19.0.0",
|
|
249
249
|
"react-dom": "^19.0.0"
|
|
250
250
|
},
|
|
251
251
|
"devDependencies": {
|
|
252
|
-
"@open-mercato/ai-assistant": "0.6.3-develop.
|
|
253
|
-
"@open-mercato/shared": "0.6.3-develop.
|
|
254
|
-
"@open-mercato/ui": "0.6.3-develop.
|
|
252
|
+
"@open-mercato/ai-assistant": "0.6.3-develop.3753.1.29e9a20dde",
|
|
253
|
+
"@open-mercato/shared": "0.6.3-develop.3753.1.29e9a20dde",
|
|
254
|
+
"@open-mercato/ui": "0.6.3-develop.3753.1.29e9a20dde",
|
|
255
255
|
"@testing-library/dom": "^10.4.1",
|
|
256
256
|
"@testing-library/jest-dom": "^6.9.1",
|
|
257
257
|
"@testing-library/react": "^16.3.1",
|
package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx
CHANGED
|
@@ -32,8 +32,8 @@ interface HostInjectionContext {
|
|
|
32
32
|
total?: number | string
|
|
33
33
|
totalMatching?: number | string
|
|
34
34
|
/** Selected row IDs from DataTable (auto-enriched when bulk actions are present). */
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
selectedRowIds?: string[]
|
|
36
|
+
selectedCount?: number
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
interface MerchandisingAssistantTriggerProps {
|
|
@@ -83,8 +83,8 @@ export function computeCatalogMerchandisingPageContext(
|
|
|
83
83
|
context: HostInjectionContext | undefined,
|
|
84
84
|
): MerchandisingPageContext {
|
|
85
85
|
const totalMatching = readNumber(context?.totalMatching ?? context?.total)
|
|
86
|
-
const selectedRowIds = Array.isArray(context?.
|
|
87
|
-
const selectedCount = selectedRowIds.length > 0 ? selectedRowIds.length : readNumber(context?.
|
|
86
|
+
const selectedRowIds = Array.isArray(context?.selectedRowIds) ? context.selectedRowIds : []
|
|
87
|
+
const selectedCount = selectedRowIds.length > 0 ? selectedRowIds.length : readNumber(context?.selectedCount)
|
|
88
88
|
return {
|
|
89
89
|
view: 'catalog.products.list',
|
|
90
90
|
entityType: 'catalog.products.list',
|
|
@@ -57,7 +57,7 @@ function readString(value: unknown): string {
|
|
|
57
57
|
return typeof value === 'string' ? value : ''
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function buildDealAnalyzerPageContext(context: HostInjectionContext | undefined): DealAnalyzerPageContext {
|
|
60
|
+
export function buildDealAnalyzerPageContext(context: HostInjectionContext | undefined): DealAnalyzerPageContext {
|
|
61
61
|
const selectedIdsRaw = Array.isArray(context?.selectedRowIds) ? context?.selectedRowIds ?? [] : []
|
|
62
62
|
const selectedIds = selectedIdsRaw.map(readString).filter((id) => id.length > 0)
|
|
63
63
|
const selectedCount = selectedIds.length > 0
|
|
@@ -46,7 +46,8 @@ export function buildMessagesInboxFilters({
|
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
id: 'hasObjects',
|
|
49
|
-
label: t('messages.filters.hasObjects', 'Has
|
|
49
|
+
label: t('messages.filters.hasObjects', 'Has related records'),
|
|
50
|
+
tooltip: t('messages.filters.hasObjectsTooltip', 'Shows messages that have Open Mercato records attached — such as orders, quotes, or customers.'),
|
|
50
51
|
type: 'select',
|
|
51
52
|
options: [
|
|
52
53
|
{ value: '', label: t('messages.filters.all', 'All') },
|
|
@@ -66,7 +67,8 @@ export function buildMessagesInboxFilters({
|
|
|
66
67
|
},
|
|
67
68
|
{
|
|
68
69
|
id: 'hasActions',
|
|
69
|
-
label: t('messages.filters.hasActions', 'Has
|
|
70
|
+
label: t('messages.filters.hasActions', 'Has action requests'),
|
|
71
|
+
tooltip: t('messages.filters.hasActionsTooltip', 'Shows messages where one or more attached records require a response (approval, rejection, or review).'),
|
|
70
72
|
type: 'select',
|
|
71
73
|
options: [
|
|
72
74
|
{ value: '', label: t('messages.filters.all', 'All') },
|
|
@@ -132,9 +132,11 @@
|
|
|
132
132
|
"messages.externalName": "Externer Name",
|
|
133
133
|
"messages.fields.type": "Nachrichtentyp",
|
|
134
134
|
"messages.filters.all": "Alle",
|
|
135
|
-
"messages.filters.hasActions": "Hat
|
|
135
|
+
"messages.filters.hasActions": "Hat Aktionsanforderungen",
|
|
136
|
+
"messages.filters.hasActionsTooltip": "Zeigt Nachrichten, bei denen mindestens ein verknüpfter Datensatz eine Antwort erfordert (Genehmigung, Ablehnung oder Prüfung).",
|
|
136
137
|
"messages.filters.hasAttachments": "Hat Anhänge",
|
|
137
|
-
"messages.filters.hasObjects": "Hat
|
|
138
|
+
"messages.filters.hasObjects": "Hat verknüpfte Datensätze",
|
|
139
|
+
"messages.filters.hasObjectsTooltip": "Zeigt Nachrichten, die verknüpfte Open Mercato-Datensätze enthalten – z. B. Aufträge, Angebote oder Kunden.",
|
|
138
140
|
"messages.filters.sender": "Absender",
|
|
139
141
|
"messages.filters.senderEmpty": "Tippe, um Benutzer zu suchen",
|
|
140
142
|
"messages.filters.since": "Gesendet nach",
|
|
@@ -132,9 +132,11 @@
|
|
|
132
132
|
"messages.externalName": "External name",
|
|
133
133
|
"messages.fields.type": "Message type",
|
|
134
134
|
"messages.filters.all": "All",
|
|
135
|
-
"messages.filters.hasActions": "Has
|
|
135
|
+
"messages.filters.hasActions": "Has action requests",
|
|
136
|
+
"messages.filters.hasActionsTooltip": "Shows messages where one or more attached records require a response (approval, rejection, or review).",
|
|
136
137
|
"messages.filters.hasAttachments": "Has attachments",
|
|
137
|
-
"messages.filters.hasObjects": "Has
|
|
138
|
+
"messages.filters.hasObjects": "Has related records",
|
|
139
|
+
"messages.filters.hasObjectsTooltip": "Shows messages that have Open Mercato records attached — such as orders, quotes, or customers.",
|
|
138
140
|
"messages.filters.sender": "Sender",
|
|
139
141
|
"messages.filters.senderEmpty": "Type to search users",
|
|
140
142
|
"messages.filters.since": "Sent after",
|
|
@@ -132,9 +132,11 @@
|
|
|
132
132
|
"messages.externalName": "Nombre externo",
|
|
133
133
|
"messages.fields.type": "Tipo de mensaje",
|
|
134
134
|
"messages.filters.all": "Todos",
|
|
135
|
-
"messages.filters.hasActions": "Tiene
|
|
135
|
+
"messages.filters.hasActions": "Tiene solicitudes de acción",
|
|
136
|
+
"messages.filters.hasActionsTooltip": "Muestra mensajes en los que uno o más registros adjuntos requieren una respuesta (aprobación, rechazo o revisión).",
|
|
136
137
|
"messages.filters.hasAttachments": "Tiene adjuntos",
|
|
137
|
-
"messages.filters.hasObjects": "Tiene
|
|
138
|
+
"messages.filters.hasObjects": "Tiene registros relacionados",
|
|
139
|
+
"messages.filters.hasObjectsTooltip": "Muestra mensajes que tienen registros de Open Mercato adjuntos, como pedidos, presupuestos o clientes.",
|
|
138
140
|
"messages.filters.sender": "Remitente",
|
|
139
141
|
"messages.filters.senderEmpty": "Escribe para buscar usuarios",
|
|
140
142
|
"messages.filters.since": "Enviado después de",
|
|
@@ -132,9 +132,11 @@
|
|
|
132
132
|
"messages.externalName": "Nazwa zewnętrzna",
|
|
133
133
|
"messages.fields.type": "Typ wiadomości",
|
|
134
134
|
"messages.filters.all": "Wszystkie",
|
|
135
|
-
"messages.filters.hasActions": "Ma
|
|
135
|
+
"messages.filters.hasActions": "Ma żądania działań",
|
|
136
|
+
"messages.filters.hasActionsTooltip": "Pokazuje wiadomości, w których co najmniej jeden powiązany rekord wymaga odpowiedzi (zatwierdzenia, odrzucenia lub przeglądu).",
|
|
136
137
|
"messages.filters.hasAttachments": "Ma załączniki",
|
|
137
|
-
"messages.filters.hasObjects": "Ma
|
|
138
|
+
"messages.filters.hasObjects": "Ma powiązane rekordy",
|
|
139
|
+
"messages.filters.hasObjectsTooltip": "Pokazuje wiadomości zawierające powiązane rekordy Open Mercato, takie jak zamówienia, oferty lub klienci.",
|
|
138
140
|
"messages.filters.sender": "Nadawca",
|
|
139
141
|
"messages.filters.senderEmpty": "Wpisz, aby wyszukać użytkowników",
|
|
140
142
|
"messages.filters.since": "Wysłane po",
|