@open-mercato/ui 0.4.5-develop-610fbb24ec → 0.4.5-develop-811deeb983

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.
@@ -5,13 +5,45 @@ import { Forward, Reply, Send } from "lucide-react";
5
5
  import { CrudForm } from "../CrudForm.js";
6
6
  import { Button } from "../../primitives/button.js";
7
7
  import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../../primitives/dialog.js";
8
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
8
9
  import { getMessageUiComponentRegistry } from "@open-mercato/core/modules/messages/components/utils/typeUiRegistry";
10
+ import { getMessageObjectType } from "@open-mercato/core/modules/messages/lib/message-objects-registry";
9
11
  import { createMessageComposeFormGroups } from "./message-compose-form-groups.js";
10
12
  import { useMessageCompose } from "./useMessageCompose.js";
13
+ function ContextObjectPreview({ contextObject }) {
14
+ const t = useT();
15
+ const registry = getMessageUiComponentRegistry();
16
+ const previewComponentKey = `${contextObject.entityModule}:${contextObject.entityType}`;
17
+ const PreviewComponent = registry.objectPreviewComponents[previewComponentKey] ?? registry.objectPreviewComponents["messages:default"];
18
+ const objectType = getMessageObjectType(contextObject.entityModule, contextObject.entityType);
19
+ if (PreviewComponent) {
20
+ return /* @__PURE__ */ jsx(
21
+ PreviewComponent,
22
+ {
23
+ entityId: contextObject.entityId,
24
+ entityModule: contextObject.entityModule,
25
+ entityType: contextObject.entityType,
26
+ actionRequired: contextObject.actionRequired,
27
+ actionType: contextObject.actionType,
28
+ actionLabel: contextObject.actionLabel,
29
+ previewData: contextObject.previewData ?? void 0,
30
+ icon: objectType?.icon
31
+ }
32
+ );
33
+ }
34
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
35
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("messages.composer.contextPreview.title", "Context object") }),
36
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
37
+ contextObject.entityModule,
38
+ ":",
39
+ contextObject.entityType
40
+ ] }),
41
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-mono text-muted-foreground", children: contextObject.entityId })
42
+ ] });
43
+ }
11
44
  function MessageComposer(props) {
12
45
  const compose = useMessageCompose(props);
13
46
  const inlineBackHref = props.inlineBackHref;
14
- const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), []);
15
47
  const SubmitIcon = compose.variant === "reply" ? Reply : compose.variant === "forward" ? Forward : Send;
16
48
  const inlineExtraActions = compose.inline ? /* @__PURE__ */ jsxs(Fragment, { children: [
17
49
  compose.variant === "compose" ? /* @__PURE__ */ jsx(
@@ -61,31 +93,8 @@ function MessageComposer(props) {
61
93
  const fallbackContextPreview = React.useMemo(() => {
62
94
  if (compose.contextPreview) return compose.contextPreview;
63
95
  if (!props.contextObject) return null;
64
- const previewComponentKey = `${props.contextObject.entityModule}:${props.contextObject.entityType}`;
65
- const PreviewComponent = messageUiRegistry.objectPreviewComponents[previewComponentKey] ?? messageUiRegistry.objectPreviewComponents["messages:default"];
66
- if (PreviewComponent) {
67
- return /* @__PURE__ */ jsx(
68
- PreviewComponent,
69
- {
70
- entityId: props.contextObject.entityId,
71
- entityModule: props.contextObject.entityModule,
72
- entityType: props.contextObject.entityType,
73
- actionRequired: props.contextObject.actionRequired,
74
- actionType: props.contextObject.actionType,
75
- actionLabel: props.contextObject.actionLabel
76
- }
77
- );
78
- }
79
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
80
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: compose.t("messages.composer.contextPreview.title", "Context object") }),
81
- /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
82
- props.contextObject.entityModule,
83
- ":",
84
- props.contextObject.entityType
85
- ] }),
86
- /* @__PURE__ */ jsx("p", { className: "text-xs font-mono text-muted-foreground", children: props.contextObject.entityId })
87
- ] });
88
- }, [compose.contextPreview, compose.t, messageUiRegistry.objectPreviewComponents, props.contextObject]);
96
+ return /* @__PURE__ */ jsx(ContextObjectPreview, { contextObject: props.contextObject });
97
+ }, [compose.contextPreview, props.contextObject]);
89
98
  const composeWithContextPreview = React.useMemo(
90
99
  () => ({ ...compose, contextPreview: fallbackContextPreview }),
91
100
  [compose, fallbackContextPreview]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/messages/MessageComposer.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Forward, Reply, Send } from 'lucide-react'\nimport { CrudForm } from '../CrudForm'\nimport { Button } from '../../primitives/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../primitives/dialog'\nimport { getMessageUiComponentRegistry } from '@open-mercato/core/modules/messages/components/utils/typeUiRegistry'\nimport { createMessageComposeFormGroups } from './message-compose-form-groups'\nimport { useMessageCompose } from './useMessageCompose'\nimport type { MessageComposerProps } from './message-composer.types'\n\nexport type {\n MessageComposerContextObject,\n MessageComposerProps,\n MessageComposerRequiredActionConfig,\n MessageComposerRequiredActionOption,\n MessageComposerVariant,\n MessageTypeItem,\n} from './message-composer.types'\n\nexport function MessageComposer(props: MessageComposerProps) {\n const compose = useMessageCompose(props)\n const inlineBackHref = props.inlineBackHref\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n const SubmitIcon = compose.variant === 'reply'\n ? Reply\n : compose.variant === 'forward'\n ? Forward\n : Send\n\n const inlineExtraActions = compose.inline\n ? (\n <>\n {compose.variant === 'compose' ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={compose.handleSaveDraft}\n disabled={compose.submitting}\n >\n {compose.t('messages.saveDraft', 'Save draft')}\n </Button>\n ) : null}\n {compose.variant !== 'compose' ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={compose.handleBack}\n disabled={compose.submitting}\n >\n {compose.t('ui.forms.actions.cancel', 'Cancel')}\n </Button>\n ) : null}\n </>\n )\n : null\n\n const dialogExtraActions = !compose.inline\n ? (\n <>\n {compose.variant === 'compose' ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={compose.handleSaveDraft}\n disabled={compose.submitting}\n >\n {compose.t('messages.saveDraft', 'Save draft')}\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={compose.handleBack}\n disabled={compose.submitting}\n >\n {compose.t('ui.forms.actions.cancel', 'Cancel')}\n </Button>\n </>\n )\n : null\n\n const backHref = compose.inline\n ? inlineBackHref === undefined\n ? '/backend/messages'\n : inlineBackHref ?? undefined\n : undefined\n\n const fallbackContextPreview = React.useMemo(() => {\n if (compose.contextPreview) return compose.contextPreview\n if (!props.contextObject) return null\n\n const previewComponentKey = `${props.contextObject.entityModule}:${props.contextObject.entityType}`\n const PreviewComponent = messageUiRegistry.objectPreviewComponents[previewComponentKey]\n ?? messageUiRegistry.objectPreviewComponents['messages:default']\n\n if (PreviewComponent) {\n return (\n <PreviewComponent\n entityId={props.contextObject.entityId}\n entityModule={props.contextObject.entityModule}\n entityType={props.contextObject.entityType}\n actionRequired={props.contextObject.actionRequired}\n actionType={props.contextObject.actionType}\n actionLabel={props.contextObject.actionLabel}\n />\n )\n }\n\n return (\n <div className=\"space-y-1\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {compose.t('messages.composer.contextPreview.title', 'Context object')}\n </p>\n <p className=\"text-sm font-medium\">\n {props.contextObject.entityModule}:{props.contextObject.entityType}\n </p>\n <p className=\"text-xs font-mono text-muted-foreground\">\n {props.contextObject.entityId}\n </p>\n </div>\n )\n }, [compose.contextPreview, compose.t, messageUiRegistry.objectPreviewComponents, props.contextObject])\n\n const composeWithContextPreview = React.useMemo(\n () => ({ ...compose, contextPreview: fallbackContextPreview }),\n [compose, fallbackContextPreview],\n )\n\n const composePanel = (\n <CrudForm<Record<string, unknown>>\n backHref={backHref}\n title={composeWithContextPreview.composerTitle}\n fields={createMessageComposeFormGroups(composeWithContextPreview)}\n initialValues={{}}\n submitLabel={composeWithContextPreview.submitLabel}\n submitIcon={SubmitIcon}\n extraActions={composeWithContextPreview.inline ? inlineExtraActions : dialogExtraActions}\n hideFooterActions\n onSubmit={async () => {\n await composeWithContextPreview.handleSubmit()\n }}\n />\n )\n\n if (compose.inline) {\n return (\n <div className=\"space-y-4\">\n {composePanel}\n </div>\n )\n }\n\n return (\n <Dialog open={Boolean(compose.open)} onOpenChange={compose.handleDialogOpenChange}>\n <DialogContent className=\"sm:max-w-3xl [&>button]:hidden\">\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{compose.composerTitle}</DialogTitle>\n </DialogHeader>\n {composePanel}\n <div className=\"flex items-center justify-end gap-2 border-t pt-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={compose.handleBack}\n disabled={compose.submitting}\n >\n {compose.t('ui.forms.actions.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => {\n void compose.handleSubmit()\n }}\n disabled={compose.submitting}\n >\n <SubmitIcon className=\"mr-2 size-4\" />\n {compose.submitLabel}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Forward, Reply, Send } from 'lucide-react'\nimport { CrudForm } from '../CrudForm'\nimport { Button } from '../../primitives/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../primitives/dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getMessageUiComponentRegistry } from '@open-mercato/core/modules/messages/components/utils/typeUiRegistry'\nimport { getMessageObjectType } from '@open-mercato/core/modules/messages/lib/message-objects-registry'\nimport { createMessageComposeFormGroups } from './message-compose-form-groups'\nimport { useMessageCompose } from './useMessageCompose'\nimport type { MessageComposerContextObject, MessageComposerProps } from './message-composer.types'\n\nexport type {\n MessageComposerContextObject,\n MessageComposerProps,\n MessageComposerRequiredActionConfig,\n MessageComposerRequiredActionOption,\n MessageComposerVariant,\n MessageTypeItem,\n} from './message-composer.types'\n\nfunction ContextObjectPreview({ contextObject }: { contextObject: MessageComposerContextObject }) {\n const t = useT()\n const registry = getMessageUiComponentRegistry()\n const previewComponentKey = `${contextObject.entityModule}:${contextObject.entityType}`\n const PreviewComponent = registry.objectPreviewComponents[previewComponentKey]\n ?? registry.objectPreviewComponents['messages:default']\n const objectType = getMessageObjectType(contextObject.entityModule, contextObject.entityType)\n\n if (PreviewComponent) {\n return (\n <PreviewComponent\n entityId={contextObject.entityId}\n entityModule={contextObject.entityModule}\n entityType={contextObject.entityType}\n actionRequired={contextObject.actionRequired}\n actionType={contextObject.actionType}\n actionLabel={contextObject.actionLabel}\n previewData={contextObject.previewData ?? undefined}\n icon={objectType?.icon}\n />\n )\n }\n\n return (\n <div className=\"space-y-1\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('messages.composer.contextPreview.title', 'Context object')}\n </p>\n <p className=\"text-sm font-medium\">\n {contextObject.entityModule}:{contextObject.entityType}\n </p>\n <p className=\"text-xs font-mono text-muted-foreground\">\n {contextObject.entityId}\n </p>\n </div>\n )\n}\n\nexport function MessageComposer(props: MessageComposerProps) {\n const compose = useMessageCompose(props)\n const inlineBackHref = props.inlineBackHref\n const SubmitIcon = compose.variant === 'reply'\n ? Reply\n : compose.variant === 'forward'\n ? Forward\n : Send\n\n const inlineExtraActions = compose.inline\n ? (\n <>\n {compose.variant === 'compose' ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={compose.handleSaveDraft}\n disabled={compose.submitting}\n >\n {compose.t('messages.saveDraft', 'Save draft')}\n </Button>\n ) : null}\n {compose.variant !== 'compose' ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={compose.handleBack}\n disabled={compose.submitting}\n >\n {compose.t('ui.forms.actions.cancel', 'Cancel')}\n </Button>\n ) : null}\n </>\n )\n : null\n\n const dialogExtraActions = !compose.inline\n ? (\n <>\n {compose.variant === 'compose' ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={compose.handleSaveDraft}\n disabled={compose.submitting}\n >\n {compose.t('messages.saveDraft', 'Save draft')}\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={compose.handleBack}\n disabled={compose.submitting}\n >\n {compose.t('ui.forms.actions.cancel', 'Cancel')}\n </Button>\n </>\n )\n : null\n\n const backHref = compose.inline\n ? inlineBackHref === undefined\n ? '/backend/messages'\n : inlineBackHref ?? undefined\n : undefined\n\n const fallbackContextPreview = React.useMemo(() => {\n if (compose.contextPreview) return compose.contextPreview\n if (!props.contextObject) return null\n return <ContextObjectPreview contextObject={props.contextObject} />\n }, [compose.contextPreview, props.contextObject])\n\n const composeWithContextPreview = React.useMemo(\n () => ({ ...compose, contextPreview: fallbackContextPreview }),\n [compose, fallbackContextPreview],\n )\n\n const composePanel = (\n <CrudForm<Record<string, unknown>>\n backHref={backHref}\n title={composeWithContextPreview.composerTitle}\n fields={createMessageComposeFormGroups(composeWithContextPreview)}\n initialValues={{}}\n submitLabel={composeWithContextPreview.submitLabel}\n submitIcon={SubmitIcon}\n extraActions={composeWithContextPreview.inline ? inlineExtraActions : dialogExtraActions}\n hideFooterActions\n onSubmit={async () => {\n await composeWithContextPreview.handleSubmit()\n }}\n />\n )\n\n if (compose.inline) {\n return (\n <div className=\"space-y-4\">\n {composePanel}\n </div>\n )\n }\n\n return (\n <Dialog open={Boolean(compose.open)} onOpenChange={compose.handleDialogOpenChange}>\n <DialogContent className=\"sm:max-w-3xl [&>button]:hidden\">\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{compose.composerTitle}</DialogTitle>\n </DialogHeader>\n {composePanel}\n <div className=\"flex items-center justify-end gap-2 border-t pt-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={compose.handleBack}\n disabled={compose.submitting}\n >\n {compose.t('ui.forms.actions.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() => {\n void compose.handleSubmit()\n }}\n disabled={compose.submitting}\n >\n <SubmitIcon className=\"mr-2 size-4\" />\n {compose.submitLabel}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,81 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ import { Button } from "@open-mercato/ui/primitives/button";
7
+ import { MessageObjectPreview } from "./MessageObjectPreview.js";
8
+ function resolveActionHref(template, entityId) {
9
+ return template.replace("{entityId}", encodeURIComponent(entityId));
10
+ }
11
+ function MessageObjectDetail(props) {
12
+ const t = useT();
13
+ const [executingActionId, setExecutingActionId] = React.useState(null);
14
+ const viewAction = props.actions.find((a) => a.id === "view");
15
+ const otherActions = props.actions.filter((a) => a.id !== "view");
16
+ const preview = /* @__PURE__ */ jsx(
17
+ MessageObjectPreview,
18
+ {
19
+ entityId: props.entityId,
20
+ entityModule: props.entityModule,
21
+ entityType: props.entityType,
22
+ snapshot: props.snapshot,
23
+ previewData: props.previewData,
24
+ actionRequired: props.actionRequired,
25
+ actionType: props.actionType,
26
+ actionLabel: props.actionLabel,
27
+ icon: props.icon
28
+ }
29
+ );
30
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded border p-3", children: [
31
+ viewAction?.href ? /* @__PURE__ */ jsx(
32
+ Link,
33
+ {
34
+ href: resolveActionHref(viewAction.href, props.entityId),
35
+ className: "block rounded-md transition-opacity hover:opacity-75",
36
+ children: preview
37
+ }
38
+ ) : preview,
39
+ otherActions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: otherActions.map((action) => {
40
+ if (action.href) {
41
+ return /* @__PURE__ */ jsx(
42
+ Button,
43
+ {
44
+ type: "button",
45
+ size: "sm",
46
+ variant: action.variant ?? "default",
47
+ asChild: true,
48
+ children: /* @__PURE__ */ jsx(Link, { href: resolveActionHref(action.href, props.entityId), children: t(action.labelKey ?? action.id, action.id) })
49
+ },
50
+ action.id
51
+ );
52
+ }
53
+ return /* @__PURE__ */ jsx(
54
+ Button,
55
+ {
56
+ type: "button",
57
+ size: "sm",
58
+ variant: action.variant ?? "default",
59
+ disabled: executingActionId !== null,
60
+ onClick: async () => {
61
+ if (executingActionId) return;
62
+ setExecutingActionId(action.id);
63
+ try {
64
+ await props.onAction(action.id, { id: props.entityId });
65
+ } finally {
66
+ setExecutingActionId(null);
67
+ }
68
+ },
69
+ children: executingActionId === action.id ? t("messages.actions.executing", "Executing...") : t(action.labelKey ?? action.id, action.id)
70
+ },
71
+ action.id
72
+ );
73
+ }) }) : null
74
+ ] });
75
+ }
76
+ var MessageObjectDetail_default = MessageObjectDetail;
77
+ export {
78
+ MessageObjectDetail,
79
+ MessageObjectDetail_default as default
80
+ };
81
+ //# sourceMappingURL=MessageObjectDetail.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/backend/messages/MessageObjectDetail.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ObjectDetailProps } from '@open-mercato/shared/modules/messages/types'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { MessageObjectPreview } from './MessageObjectPreview'\n\nfunction resolveActionHref(template: string, entityId: string): string {\n return template.replace('{entityId}', encodeURIComponent(entityId))\n}\n\nexport function MessageObjectDetail(props: ObjectDetailProps) {\n const t = useT()\n const [executingActionId, setExecutingActionId] = React.useState<string | null>(null)\n\n const viewAction = props.actions.find((a) => a.id === 'view')\n const otherActions = props.actions.filter((a) => a.id !== 'view')\n\n const preview = (\n <MessageObjectPreview\n entityId={props.entityId}\n entityModule={props.entityModule}\n entityType={props.entityType}\n snapshot={props.snapshot}\n previewData={props.previewData}\n actionRequired={props.actionRequired}\n actionType={props.actionType}\n actionLabel={props.actionLabel}\n icon={props.icon}\n />\n )\n\n return (\n <div className=\"space-y-3 rounded border p-3\">\n {viewAction?.href ? (\n <Link\n href={resolveActionHref(viewAction.href, props.entityId)}\n className=\"block rounded-md transition-opacity hover:opacity-75\"\n >\n {preview}\n </Link>\n ) : (\n preview\n )}\n\n {otherActions.length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {otherActions.map((action) => {\n if (action.href) {\n return (\n <Button\n key={action.id}\n type=\"button\"\n size=\"sm\"\n variant={action.variant ?? 'default'}\n asChild\n >\n <Link href={resolveActionHref(action.href, props.entityId)}>\n {t(action.labelKey ?? action.id, action.id)}\n </Link>\n </Button>\n )\n }\n return (\n <Button\n key={action.id}\n type=\"button\"\n size=\"sm\"\n variant={action.variant ?? 'default'}\n disabled={executingActionId !== null}\n onClick={async () => {\n if (executingActionId) return\n setExecutingActionId(action.id)\n try {\n await props.onAction(action.id, { id: props.entityId })\n } finally {\n setExecutingActionId(null)\n }\n }}\n >\n {executingActionId === action.id\n ? t('messages.actions.executing', 'Executing...')\n : t(action.labelKey ?? action.id, action.id)}\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n )\n}\n\nexport default MessageObjectDetail\n"],
5
+ "mappings": ";AAqBI,cAcA,YAdA;AAnBJ,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,YAAY;AAErB,SAAS,cAAc;AACvB,SAAS,4BAA4B;AAErC,SAAS,kBAAkB,UAAkB,UAA0B;AACrE,SAAO,SAAS,QAAQ,cAAc,mBAAmB,QAAQ,CAAC;AACpE;AAEO,SAAS,oBAAoB,OAA0B;AAC5D,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAwB,IAAI;AAEpF,QAAM,aAAa,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM;AAC5D,QAAM,eAAe,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,MAAM;AAEhE,QAAM,UACJ;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,MAAM;AAAA,MAChB,cAAc,MAAM;AAAA,MACpB,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,aAAa,MAAM;AAAA,MACnB,gBAAgB,MAAM;AAAA,MACtB,YAAY,MAAM;AAAA,MAClB,aAAa,MAAM;AAAA,MACnB,MAAM,MAAM;AAAA;AAAA,EACd;AAGF,SACE,qBAAC,SAAI,WAAU,gCACZ;AAAA,gBAAY,OACX;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,kBAAkB,WAAW,MAAM,MAAM,QAAQ;AAAA,QACvD,WAAU;AAAA,QAET;AAAA;AAAA,IACH,IAEA;AAAA,IAGD,aAAa,SAAS,IACrB,oBAAC,SAAI,WAAU,wBACZ,uBAAa,IAAI,CAAC,WAAW;AAC5B,UAAI,OAAO,MAAM;AACf,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,MAAK;AAAA,YACL,SAAS,OAAO,WAAW;AAAA,YAC3B,SAAO;AAAA,YAEP,8BAAC,QAAK,MAAM,kBAAkB,OAAO,MAAM,MAAM,QAAQ,GACtD,YAAE,OAAO,YAAY,OAAO,IAAI,OAAO,EAAE,GAC5C;AAAA;AAAA,UARK,OAAO;AAAA,QASd;AAAA,MAEJ;AACA,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,SAAS,OAAO,WAAW;AAAA,UAC3B,UAAU,sBAAsB;AAAA,UAChC,SAAS,YAAY;AACnB,gBAAI,kBAAmB;AACvB,iCAAqB,OAAO,EAAE;AAC9B,gBAAI;AACF,oBAAM,MAAM,SAAS,OAAO,IAAI,EAAE,IAAI,MAAM,SAAS,CAAC;AAAA,YACxD,UAAE;AACA,mCAAqB,IAAI;AAAA,YAC3B;AAAA,UACF;AAAA,UAEC,gCAAsB,OAAO,KAC1B,EAAE,8BAA8B,cAAc,IAC9C,EAAE,OAAO,YAAY,OAAO,IAAI,OAAO,EAAE;AAAA;AAAA,QAjBxC,OAAO;AAAA,MAkBd;AAAA,IAEJ,CAAC,GACH,IACE;AAAA,KACN;AAEJ;AAEA,IAAO,8BAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Box } from "lucide-react";
4
+ import * as lucideIcons from "lucide-react";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ import { Badge } from "@open-mercato/ui/primitives/badge";
7
+ function resolveIcon(name) {
8
+ if (!name) return Box;
9
+ const key = name.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
10
+ const candidate = lucideIcons[key];
11
+ if (typeof candidate === "function" || typeof candidate === "object" && candidate !== null && "$$typeof" in candidate) {
12
+ return candidate;
13
+ }
14
+ return Box;
15
+ }
16
+ function MessageObjectPreview({
17
+ previewData,
18
+ actionRequired,
19
+ actionLabel,
20
+ icon
21
+ }) {
22
+ const t = useT();
23
+ const Icon = resolveIcon(icon);
24
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 rounded-md border bg-muted/20 p-3", children: [
25
+ /* @__PURE__ */ jsx(Icon, { className: "mt-0.5 h-4 w-4 text-muted-foreground" }),
26
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
27
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
28
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: previewData?.title || "" }),
29
+ actionRequired ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: actionLabel || t("messages.composer.objectActionRequired", "Action required") }) : null
30
+ ] }),
31
+ previewData?.subtitle ? /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: previewData.subtitle }) : null,
32
+ previewData?.status ? /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: previewData.status }) : null,
33
+ previewData?.metadata && Object.keys(previewData.metadata).length > 0 ? /* @__PURE__ */ jsx("dl", { className: "space-y-1 pt-1", children: Object.entries(previewData.metadata).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs text-muted-foreground", children: [
34
+ /* @__PURE__ */ jsxs("dt", { className: "font-medium capitalize", children: [
35
+ key,
36
+ ":"
37
+ ] }),
38
+ /* @__PURE__ */ jsx("dd", { className: "truncate", children: value })
39
+ ] }, key)) }) : null
40
+ ] })
41
+ ] });
42
+ }
43
+ var MessageObjectPreview_default = MessageObjectPreview;
44
+ export {
45
+ MessageObjectPreview,
46
+ MessageObjectPreview_default as default
47
+ };
48
+ //# sourceMappingURL=MessageObjectPreview.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/backend/messages/MessageObjectPreview.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport { Box, type LucideIcon } from 'lucide-react'\nimport * as lucideIcons from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ObjectPreviewProps } from '@open-mercato/shared/modules/messages/types'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\n\nfunction resolveIcon(name: string | undefined): LucideIcon {\n if (!name) return Box\n const key = name\n .split('-')\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join('') as keyof typeof lucideIcons\n const candidate = lucideIcons[key]\n if (typeof candidate === 'function' || (typeof candidate === 'object' && candidate !== null && '$$typeof' in candidate)) {\n return candidate as LucideIcon\n }\n return Box\n}\n\nexport function MessageObjectPreview({\n previewData,\n actionRequired,\n actionLabel,\n icon,\n}: ObjectPreviewProps) {\n const t = useT()\n const Icon = resolveIcon(icon)\n\n return (\n <div className=\"flex items-start gap-3 rounded-md border bg-muted/20 p-3\">\n <Icon className=\"mt-0.5 h-4 w-4 text-muted-foreground\" />\n <div className=\"min-w-0 flex-1 space-y-1\">\n <div className=\"flex items-center gap-2\">\n <p className=\"truncate text-sm font-medium\">{previewData?.title || ''}</p>\n {actionRequired ? (\n <Badge variant=\"secondary\" className=\"text-xs\">\n {actionLabel || t('messages.composer.objectActionRequired', 'Action required')}\n </Badge>\n ) : null}\n </div>\n {previewData?.subtitle ? (\n <p className=\"truncate text-xs text-muted-foreground\">{previewData.subtitle}</p>\n ) : null}\n {previewData?.status ? (\n <Badge variant=\"outline\" className=\"text-xs\">{previewData.status}</Badge>\n ) : null}\n {previewData?.metadata && Object.keys(previewData.metadata).length > 0 ? (\n <dl className=\"space-y-1 pt-1\">\n {Object.entries(previewData.metadata).map(([key, value]) => (\n <div key={key} className=\"flex items-start gap-2 text-xs text-muted-foreground\">\n <dt className=\"font-medium capitalize\">{key}:</dt>\n <dd className=\"truncate\">{value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n </div>\n )\n}\n\nexport default MessageObjectPreview\n"],
5
+ "mappings": ";AAgCM,cAEE,YAFF;AA9BN,SAAS,WAA4B;AACrC,YAAY,iBAAiB;AAC7B,SAAS,YAAY;AAErB,SAAS,aAAa;AAEtB,SAAS,YAAY,MAAsC;AACzD,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,MAAM,KACT,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,CAAC,EACjD,KAAK,EAAE;AACV,QAAM,YAAY,YAAY,GAAG;AACjC,MAAI,OAAO,cAAc,cAAe,OAAO,cAAc,YAAY,cAAc,QAAQ,cAAc,WAAY;AACvH,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,IAAI,KAAK;AACf,QAAM,OAAO,YAAY,IAAI;AAE7B,SACE,qBAAC,SAAI,WAAU,4DACb;AAAA,wBAAC,QAAK,WAAU,wCAAuC;AAAA,IACvD,qBAAC,SAAI,WAAU,4BACb;AAAA,2BAAC,SAAI,WAAU,2BACb;AAAA,4BAAC,OAAE,WAAU,gCAAgC,uBAAa,SAAS,IAAG;AAAA,QACrE,iBACC,oBAAC,SAAM,SAAQ,aAAY,WAAU,WAClC,yBAAe,EAAE,0CAA0C,iBAAiB,GAC/E,IACE;AAAA,SACN;AAAA,MACC,aAAa,WACZ,oBAAC,OAAE,WAAU,0CAA0C,sBAAY,UAAS,IAC1E;AAAA,MACH,aAAa,SACZ,oBAAC,SAAM,SAAQ,WAAU,WAAU,WAAW,sBAAY,QAAO,IAC/D;AAAA,MACH,aAAa,YAAY,OAAO,KAAK,YAAY,QAAQ,EAAE,SAAS,IACnE,oBAAC,QAAG,WAAU,kBACX,iBAAO,QAAQ,YAAY,QAAQ,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MACpD,qBAAC,SAAc,WAAU,wDACvB;AAAA,6BAAC,QAAG,WAAU,0BAA0B;AAAA;AAAA,UAAI;AAAA,WAAC;AAAA,QAC7C,oBAAC,QAAG,WAAU,YAAY,iBAAM;AAAA,WAFxB,GAGV,CACD,GACH,IACE;AAAA,OACN;AAAA,KACF;AAEJ;AAEA,IAAO,+BAAQ;",
6
+ "names": []
7
+ }
@@ -5,6 +5,7 @@ import { Button } from "../../primitives/button.js";
5
5
  import { Input } from "../../primitives/input.js";
6
6
  import { Label } from "../../primitives/label.js";
7
7
  import { getMessageUiComponentRegistry } from "@open-mercato/core/modules/messages/components/utils/typeUiRegistry";
8
+ import { getMessageObjectType } from "@open-mercato/core/modules/messages/lib/message-objects-registry";
8
9
  function MessageObjectRecordPicker({
9
10
  search,
10
11
  onSearchChange,
@@ -43,6 +44,7 @@ function MessageObjectRecordPicker({
43
44
  const resolvedEntityType = item.entityType || entityType;
44
45
  const componentKey = resolvedModule && resolvedEntityType ? `${resolvedModule}:${resolvedEntityType}` : null;
45
46
  const PreviewComponent = (componentKey ? messageUiRegistry.objectPreviewComponents[componentKey] : null) ?? messageUiRegistry.objectPreviewComponents["messages:default"] ?? null;
47
+ const objectType = resolvedModule && resolvedEntityType ? getMessageObjectType(resolvedModule, resolvedEntityType) : null;
46
48
  return /* @__PURE__ */ jsx(
47
49
  "div",
48
50
  {
@@ -58,7 +60,8 @@ function MessageObjectRecordPicker({
58
60
  previewData: {
59
61
  title: item.label,
60
62
  subtitle: item.subtitle || void 0
61
- }
63
+ },
64
+ icon: objectType?.icon
62
65
  }
63
66
  ) : /* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
64
67
  /* @__PURE__ */ jsx("p", { className: "font-medium", children: item.label }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/messages/MessageObjectRecordPicker.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { Input } from '../../primitives/input'\nimport { Label } from '../../primitives/label'\nimport { getMessageUiComponentRegistry } from '@open-mercato/core/modules/messages/components/utils/typeUiRegistry'\n\nexport type MessageObjectOptionItem = {\n id: string\n label: string\n subtitle?: string\n entityModule?: string\n entityType?: string\n snapshot?: Record<string, unknown>\n}\n\nexport type MessageObjectRecordPickerProps = {\n search: string\n onSearchChange: (value: string) => void\n selectedId: string\n onSelectedIdChange: (value: string) => void\n items: MessageObjectOptionItem[]\n isLoading: boolean\n error: string | null\n onRetry: () => void\n entityModule?: string\n entityType?: string\n}\n\nexport function MessageObjectRecordPicker({\n search,\n onSearchChange,\n selectedId,\n onSelectedIdChange,\n items,\n isLoading,\n error,\n onRetry,\n entityModule,\n entityType,\n}: MessageObjectRecordPickerProps) {\n const t = useT()\n const messageUiRegistry = getMessageUiComponentRegistry()\n\n return (\n <div className=\"space-y-2\">\n <Label htmlFor=\"messages-object-record-search\">\n {t('messages.composer.objectPicker.recordSearchLabel', 'Search records')}\n </Label>\n <Input\n id=\"messages-object-record-search\"\n value={search}\n onChange={(event) => onSearchChange(event.target.value)}\n placeholder={t('messages.composer.objectPicker.recordSearchPlaceholder', 'Type to find a record')}\n />\n\n {isLoading ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('messages.composer.objectPicker.loadingRecords', 'Loading records...')}\n </p>\n ) : null}\n\n {error ? (\n <div className=\"space-y-2 rounded border border-destructive/40 bg-destructive/5 p-2 text-xs text-destructive\">\n <p>{error}</p>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={onRetry}>\n {t('common.retry', 'Retry')}\n </Button>\n </div>\n ) : null}\n\n <div className=\"space-y-2\">\n <Label>\n {t('messages.composer.objectPicker.recordLabel', 'Record')}\n </Label>\n {!selectedId && (\n <p className=\"text-sm text-muted-foreground\">\n {t('messages.composer.objectPicker.recordPlaceholder', 'Select record')}\n </p>\n )}\n <div className=\"space-y-2 max-h-64 overflow-y-auto\">\n {items.map((item) => {\n const resolvedModule = item.entityModule || entityModule\n const resolvedEntityType = item.entityType || entityType\n const componentKey = resolvedModule && resolvedEntityType\n ? `${resolvedModule}:${resolvedEntityType}`\n : null\n const PreviewComponent = (componentKey\n ? messageUiRegistry.objectPreviewComponents[componentKey]\n : null) ?? messageUiRegistry.objectPreviewComponents['messages:default'] ?? null\n\n return (\n <div\n key={item.id}\n className={`cursor-pointer rounded-md border p-2 transition-colors ${\n selectedId === item.id\n ? 'border-primary bg-primary/5'\n : 'border-border hover:bg-muted/50'\n }`}\n onClick={() => onSelectedIdChange(item.id)}\n >\n {PreviewComponent ? (\n <PreviewComponent\n entityId={item.id}\n entityModule={item.entityModule || entityModule || ''}\n entityType={item.entityType || entityType || ''}\n snapshot={item.snapshot}\n previewData={{\n title: item.label,\n subtitle: item.subtitle || undefined,\n }}\n />\n ) : (\n <div className=\"text-sm\">\n <p className=\"font-medium\">{item.label}</p>\n {item.subtitle && (\n <p className=\"text-muted-foreground text-xs\">{item.subtitle}</p>\n )}\n </div>\n )}\n </div>\n )\n })}\n </div>\n </div>\n\n {!isLoading && !error && items.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('messages.composer.objectPicker.noRecords', 'No records found for this object type.')}\n </p>\n ) : null}\n </div>\n )\n}\n"],
5
- "mappings": ";AAgDM,cAiBE,YAjBF;AA7CN,SAAS,YAAY;AACrB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,qCAAqC;AAwBvC,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmC;AACjC,QAAM,IAAI,KAAK;AACf,QAAM,oBAAoB,8BAA8B;AAExD,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,SAAM,SAAQ,iCACZ,YAAE,oDAAoD,gBAAgB,GACzE;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,IAAG;AAAA,QACH,OAAO;AAAA,QACP,UAAU,CAAC,UAAU,eAAe,MAAM,OAAO,KAAK;AAAA,QACtD,aAAa,EAAE,0DAA0D,uBAAuB;AAAA;AAAA,IAClG;AAAA,IAEC,YACC,oBAAC,OAAE,WAAU,iCACV,YAAE,iDAAiD,oBAAoB,GAC1E,IACE;AAAA,IAEH,QACC,qBAAC,SAAI,WAAU,gGACb;AAAA,0BAAC,OAAG,iBAAM;AAAA,MACV,oBAAC,UAAO,MAAK,UAAS,MAAK,MAAK,SAAQ,WAAU,SAAS,SACxD,YAAE,gBAAgB,OAAO,GAC5B;AAAA,OACF,IACE;AAAA,IAEJ,qBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,SACE,YAAE,8CAA8C,QAAQ,GAC3D;AAAA,MACC,CAAC,cACA,oBAAC,OAAE,WAAU,iCACV,YAAE,oDAAoD,eAAe,GACxE;AAAA,MAEF,oBAAC,SAAI,WAAU,sCACZ,gBAAM,IAAI,CAAC,SAAS;AACnB,cAAM,iBAAiB,KAAK,gBAAgB;AAC5C,cAAM,qBAAqB,KAAK,cAAc;AAC9C,cAAM,eAAe,kBAAkB,qBACnC,GAAG,cAAc,IAAI,kBAAkB,KACvC;AACJ,cAAM,oBAAoB,eACtB,kBAAkB,wBAAwB,YAAY,IACtD,SAAS,kBAAkB,wBAAwB,kBAAkB,KAAK;AAE9E,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,WAAW,0DACT,eAAe,KAAK,KAChB,gCACA,iCACN;AAAA,YACA,SAAS,MAAM,mBAAmB,KAAK,EAAE;AAAA,YAExC,6BACC;AAAA,cAAC;AAAA;AAAA,gBACC,UAAU,KAAK;AAAA,gBACf,cAAc,KAAK,gBAAgB,gBAAgB;AAAA,gBACnD,YAAY,KAAK,cAAc,cAAc;AAAA,gBAC7C,UAAU,KAAK;AAAA,gBACf,aAAa;AAAA,kBACX,OAAO,KAAK;AAAA,kBACZ,UAAU,KAAK,YAAY;AAAA,gBAC7B;AAAA;AAAA,YACF,IAEA,qBAAC,SAAI,WAAU,WACb;AAAA,kCAAC,OAAE,WAAU,eAAe,eAAK,OAAM;AAAA,cACtC,KAAK,YACJ,oBAAC,OAAE,WAAU,iCAAiC,eAAK,UAAS;AAAA,eAEhE;AAAA;AAAA,UAzBG,KAAK;AAAA,QA2BZ;AAAA,MAEJ,CAAC,GACH;AAAA,OACF;AAAA,IAEC,CAAC,aAAa,CAAC,SAAS,MAAM,WAAW,IACxC,oBAAC,OAAE,WAAU,iCACV,YAAE,4CAA4C,wCAAwC,GACzF,IACE;AAAA,KACN;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { Input } from '../../primitives/input'\nimport { Label } from '../../primitives/label'\nimport { getMessageUiComponentRegistry } from '@open-mercato/core/modules/messages/components/utils/typeUiRegistry'\nimport { getMessageObjectType } from '@open-mercato/core/modules/messages/lib/message-objects-registry'\n\nexport type MessageObjectOptionItem = {\n id: string\n label: string\n subtitle?: string\n entityModule?: string\n entityType?: string\n snapshot?: Record<string, unknown>\n}\n\nexport type MessageObjectRecordPickerProps = {\n search: string\n onSearchChange: (value: string) => void\n selectedId: string\n onSelectedIdChange: (value: string) => void\n items: MessageObjectOptionItem[]\n isLoading: boolean\n error: string | null\n onRetry: () => void\n entityModule?: string\n entityType?: string\n}\n\nexport function MessageObjectRecordPicker({\n search,\n onSearchChange,\n selectedId,\n onSelectedIdChange,\n items,\n isLoading,\n error,\n onRetry,\n entityModule,\n entityType,\n}: MessageObjectRecordPickerProps) {\n const t = useT()\n const messageUiRegistry = getMessageUiComponentRegistry()\n\n return (\n <div className=\"space-y-2\">\n <Label htmlFor=\"messages-object-record-search\">\n {t('messages.composer.objectPicker.recordSearchLabel', 'Search records')}\n </Label>\n <Input\n id=\"messages-object-record-search\"\n value={search}\n onChange={(event) => onSearchChange(event.target.value)}\n placeholder={t('messages.composer.objectPicker.recordSearchPlaceholder', 'Type to find a record')}\n />\n\n {isLoading ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('messages.composer.objectPicker.loadingRecords', 'Loading records...')}\n </p>\n ) : null}\n\n {error ? (\n <div className=\"space-y-2 rounded border border-destructive/40 bg-destructive/5 p-2 text-xs text-destructive\">\n <p>{error}</p>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={onRetry}>\n {t('common.retry', 'Retry')}\n </Button>\n </div>\n ) : null}\n\n <div className=\"space-y-2\">\n <Label>\n {t('messages.composer.objectPicker.recordLabel', 'Record')}\n </Label>\n {!selectedId && (\n <p className=\"text-sm text-muted-foreground\">\n {t('messages.composer.objectPicker.recordPlaceholder', 'Select record')}\n </p>\n )}\n <div className=\"space-y-2 max-h-64 overflow-y-auto\">\n {items.map((item) => {\n const resolvedModule = item.entityModule || entityModule\n const resolvedEntityType = item.entityType || entityType\n const componentKey = resolvedModule && resolvedEntityType\n ? `${resolvedModule}:${resolvedEntityType}`\n : null\n const PreviewComponent = (componentKey\n ? messageUiRegistry.objectPreviewComponents[componentKey]\n : null) ?? messageUiRegistry.objectPreviewComponents['messages:default'] ?? null\n const objectType = resolvedModule && resolvedEntityType\n ? getMessageObjectType(resolvedModule, resolvedEntityType)\n : null\n\n return (\n <div\n key={item.id}\n className={`cursor-pointer rounded-md border p-2 transition-colors ${\n selectedId === item.id\n ? 'border-primary bg-primary/5'\n : 'border-border hover:bg-muted/50'\n }`}\n onClick={() => onSelectedIdChange(item.id)}\n >\n {PreviewComponent ? (\n <PreviewComponent\n entityId={item.id}\n entityModule={item.entityModule || entityModule || ''}\n entityType={item.entityType || entityType || ''}\n snapshot={item.snapshot}\n previewData={{\n title: item.label,\n subtitle: item.subtitle || undefined,\n }}\n icon={objectType?.icon}\n />\n ) : (\n <div className=\"text-sm\">\n <p className=\"font-medium\">{item.label}</p>\n {item.subtitle && (\n <p className=\"text-muted-foreground text-xs\">{item.subtitle}</p>\n )}\n </div>\n )}\n </div>\n )\n })}\n </div>\n </div>\n\n {!isLoading && !error && items.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('messages.composer.objectPicker.noRecords', 'No records found for this object type.')}\n </p>\n ) : null}\n </div>\n )\n}\n"],
5
+ "mappings": ";AAiDM,cAiBE,YAjBF;AA9CN,SAAS,YAAY;AACrB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,qCAAqC;AAC9C,SAAS,4BAA4B;AAwB9B,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmC;AACjC,QAAM,IAAI,KAAK;AACf,QAAM,oBAAoB,8BAA8B;AAExD,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,SAAM,SAAQ,iCACZ,YAAE,oDAAoD,gBAAgB,GACzE;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,IAAG;AAAA,QACH,OAAO;AAAA,QACP,UAAU,CAAC,UAAU,eAAe,MAAM,OAAO,KAAK;AAAA,QACtD,aAAa,EAAE,0DAA0D,uBAAuB;AAAA;AAAA,IAClG;AAAA,IAEC,YACC,oBAAC,OAAE,WAAU,iCACV,YAAE,iDAAiD,oBAAoB,GAC1E,IACE;AAAA,IAEH,QACC,qBAAC,SAAI,WAAU,gGACb;AAAA,0BAAC,OAAG,iBAAM;AAAA,MACV,oBAAC,UAAO,MAAK,UAAS,MAAK,MAAK,SAAQ,WAAU,SAAS,SACxD,YAAE,gBAAgB,OAAO,GAC5B;AAAA,OACF,IACE;AAAA,IAEJ,qBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,SACE,YAAE,8CAA8C,QAAQ,GAC3D;AAAA,MACC,CAAC,cACA,oBAAC,OAAE,WAAU,iCACV,YAAE,oDAAoD,eAAe,GACxE;AAAA,MAEF,oBAAC,SAAI,WAAU,sCACZ,gBAAM,IAAI,CAAC,SAAS;AACnB,cAAM,iBAAiB,KAAK,gBAAgB;AAC5C,cAAM,qBAAqB,KAAK,cAAc;AAC9C,cAAM,eAAe,kBAAkB,qBACnC,GAAG,cAAc,IAAI,kBAAkB,KACvC;AACJ,cAAM,oBAAoB,eACtB,kBAAkB,wBAAwB,YAAY,IACtD,SAAS,kBAAkB,wBAAwB,kBAAkB,KAAK;AAC9E,cAAM,aAAa,kBAAkB,qBACjC,qBAAqB,gBAAgB,kBAAkB,IACvD;AAEJ,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,WAAW,0DACT,eAAe,KAAK,KAChB,gCACA,iCACN;AAAA,YACA,SAAS,MAAM,mBAAmB,KAAK,EAAE;AAAA,YAExC,6BACC;AAAA,cAAC;AAAA;AAAA,gBACC,UAAU,KAAK;AAAA,gBACf,cAAc,KAAK,gBAAgB,gBAAgB;AAAA,gBACnD,YAAY,KAAK,cAAc,cAAc;AAAA,gBAC7C,UAAU,KAAK;AAAA,gBACf,aAAa;AAAA,kBACX,OAAO,KAAK;AAAA,kBACZ,UAAU,KAAK,YAAY;AAAA,gBAC7B;AAAA,gBACA,MAAM,YAAY;AAAA;AAAA,YACpB,IAEA,qBAAC,SAAI,WAAU,WACb;AAAA,kCAAC,OAAE,WAAU,eAAe,eAAK,OAAM;AAAA,cACtC,KAAK,YACJ,oBAAC,OAAE,WAAU,iCAAiC,eAAK,UAAS;AAAA,eAEhE;AAAA;AAAA,UA1BG,KAAK;AAAA,QA4BZ;AAAA,MAEJ,CAAC,GACH;AAAA,OACF;AAAA,IAEC,CAAC,aAAa,CAAC,SAAS,MAAM,WAAW,IACxC,oBAAC,OAAE,WAAU,iCACV,YAAE,4CAA4C,wCAAwC,GACzF,IACE;AAAA,KACN;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Send } from "lucide-react";
4
+ import Link from "next/link";
5
+ import { ExternalLink, Send } from "lucide-react";
5
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
7
  import { Button } from "../../primitives/button.js";
7
8
  import {
@@ -13,10 +14,8 @@ function SendObjectMessageDialog({
13
14
  lockedType = "messages.defaultWithObjects",
14
15
  requiredActionConfig = null,
15
16
  disabled = false,
16
- contextPreview = null,
17
- children = null,
18
- onSuccess,
19
- renderTrigger
17
+ viewHref = null,
18
+ onSuccess
20
19
  }) {
21
20
  const t = useT();
22
21
  const [open, setOpen] = React.useState(false);
@@ -29,23 +28,35 @@ function SendObjectMessageDialog({
29
28
  entityType: object.entityType,
30
29
  entityId: object.entityId,
31
30
  sourceEntityType: object.sourceEntityType ?? null,
32
- sourceEntityId: object.sourceEntityId ?? null
33
- }), [object.entityId, object.entityModule, object.entityType, object.sourceEntityId, object.sourceEntityType]);
34
- const trigger = renderTrigger ? renderTrigger({ openComposer, disabled }) : /* @__PURE__ */ jsx(
35
- Button,
36
- {
37
- type: "button",
38
- size: "icon",
39
- variant: "outline",
40
- disabled,
41
- onClick: openComposer,
42
- "aria-label": t("messages.compose", "Compose message"),
43
- title: t("messages.compose", "Compose message"),
44
- children: /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
45
- }
46
- );
31
+ sourceEntityId: object.sourceEntityId ?? null,
32
+ previewData: object.previewData ?? null
33
+ }), [object.entityId, object.entityModule, object.entityType, object.sourceEntityId, object.sourceEntityType, object.previewData]);
47
34
  return /* @__PURE__ */ jsxs(Fragment, { children: [
48
- trigger,
35
+ viewHref ? /* @__PURE__ */ jsx(
36
+ Button,
37
+ {
38
+ type: "button",
39
+ size: "icon",
40
+ variant: "outline",
41
+ asChild: true,
42
+ "aria-label": t("common.view", "View"),
43
+ title: t("common.view", "View"),
44
+ children: /* @__PURE__ */ jsx(Link, { href: viewHref, children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" }) })
45
+ }
46
+ ) : null,
47
+ /* @__PURE__ */ jsx(
48
+ Button,
49
+ {
50
+ type: "button",
51
+ size: "icon",
52
+ variant: "ghost",
53
+ disabled,
54
+ onClick: openComposer,
55
+ "aria-label": t("messages.compose", "Compose message"),
56
+ title: t("messages.compose", "Compose message"),
57
+ children: /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
58
+ }
59
+ ),
49
60
  /* @__PURE__ */ jsx(
50
61
  MessageComposer,
51
62
  {
@@ -55,7 +66,6 @@ function SendObjectMessageDialog({
55
66
  lockedType,
56
67
  contextObject,
57
68
  requiredActionConfig,
58
- contextPreview: contextPreview ?? children,
59
69
  defaultValues,
60
70
  onSuccess
61
71
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/messages/SendObjectMessageDialog.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Send } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport {\n MessageComposer,\n type MessageComposerContextObject,\n type MessageComposerProps,\n type MessageComposerRequiredActionConfig,\n} from './MessageComposer'\n\nexport type SendObjectMessageDialogProps = {\n object: MessageComposerContextObject\n defaultValues?: MessageComposerProps['defaultValues']\n lockedType?: string | null\n requiredActionConfig?: MessageComposerRequiredActionConfig | null\n disabled?: boolean\n contextPreview?: React.ReactNode\n children?: React.ReactNode\n onSuccess?: MessageComposerProps['onSuccess']\n renderTrigger?: (params: { openComposer: () => void; disabled: boolean }) => React.ReactNode\n}\n\nexport function SendObjectMessageDialog({\n object,\n defaultValues,\n lockedType = 'messages.defaultWithObjects',\n requiredActionConfig = null,\n disabled = false,\n contextPreview = null,\n children = null,\n onSuccess,\n renderTrigger,\n}: SendObjectMessageDialogProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n\n const openComposer = React.useCallback(() => {\n if (disabled) return\n setOpen(true)\n }, [disabled])\n const contextObject = React.useMemo(() => ({\n entityModule: object.entityModule,\n entityType: object.entityType,\n entityId: object.entityId,\n sourceEntityType: object.sourceEntityType ?? null,\n sourceEntityId: object.sourceEntityId ?? null,\n }), [object.entityId, object.entityModule, object.entityType, object.sourceEntityId, object.sourceEntityType])\n\n const trigger = renderTrigger\n ? renderTrigger({ openComposer, disabled })\n : (\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"outline\"\n disabled={disabled}\n onClick={openComposer}\n aria-label={t('messages.compose', 'Compose message')}\n title={t('messages.compose', 'Compose message')}\n >\n <Send className=\"h-4 w-4\" />\n </Button>\n )\n \n return (\n <>\n {trigger}\n <MessageComposer\n variant=\"compose\"\n open={open}\n onOpenChange={setOpen}\n lockedType={lockedType}\n contextObject={contextObject}\n requiredActionConfig={requiredActionConfig}\n contextPreview={contextPreview ?? children}\n defaultValues={defaultValues}\n onSuccess={onSuccess}\n />\n </>\n )\n}\n"],
5
- "mappings": ";AA+DQ,SAKJ,UALI,KAKJ,YALI;AA7DR,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,OAIK;AAcA,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,uBAAuB;AAAA,EACvB,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX;AAAA,EACA;AACF,GAAiC;AAC/B,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,KAAK;AAE5C,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,QAAI,SAAU;AACd,YAAQ,IAAI;AAAA,EACd,GAAG,CAAC,QAAQ,CAAC;AACb,QAAM,gBAAgB,MAAM,QAAQ,OAAO;AAAA,IACzC,cAAc,OAAO;AAAA,IACrB,YAAY,OAAO;AAAA,IACnB,UAAU,OAAO;AAAA,IACjB,kBAAkB,OAAO,oBAAoB;AAAA,IAC7C,gBAAgB,OAAO,kBAAkB;AAAA,EAC3C,IAAI,CAAC,OAAO,UAAU,OAAO,cAAc,OAAO,YAAY,OAAO,gBAAgB,OAAO,gBAAgB,CAAC;AAE7G,QAAM,UAAU,gBACZ,cAAc,EAAE,cAAc,SAAS,CAAC,IAExC;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,MAAK;AAAA,MACL,SAAQ;AAAA,MACR;AAAA,MACA,SAAS;AAAA,MACT,cAAY,EAAE,oBAAoB,iBAAiB;AAAA,MACnD,OAAO,EAAE,oBAAoB,iBAAiB;AAAA,MAE9C,8BAAC,QAAK,WAAU,WAAU;AAAA;AAAA,EAC5B;AAGJ,SACE,iCACG;AAAA;AAAA,IACD;AAAA,MAAC;AAAA;AAAA,QACC,SAAQ;AAAA,QACR;AAAA,QACA,cAAc;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,QACA,gBAAgB,kBAAkB;AAAA,QAClC;AAAA,QACA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { ExternalLink, Send } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport {\n MessageComposer,\n type MessageComposerContextObject,\n type MessageComposerProps,\n type MessageComposerRequiredActionConfig,\n} from './MessageComposer'\n\nexport type SendObjectMessageDialogProps = {\n object: MessageComposerContextObject\n defaultValues?: MessageComposerProps['defaultValues']\n lockedType?: string | null\n requiredActionConfig?: MessageComposerRequiredActionConfig | null\n disabled?: boolean\n viewHref?: string | null\n onSuccess?: MessageComposerProps['onSuccess']\n}\n\nexport function SendObjectMessageDialog({\n object,\n defaultValues,\n lockedType = 'messages.defaultWithObjects',\n requiredActionConfig = null,\n disabled = false,\n viewHref = null,\n onSuccess,\n}: SendObjectMessageDialogProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n\n const openComposer = React.useCallback(() => {\n if (disabled) return\n setOpen(true)\n }, [disabled])\n const contextObject = React.useMemo(() => ({\n entityModule: object.entityModule,\n entityType: object.entityType,\n entityId: object.entityId,\n sourceEntityType: object.sourceEntityType ?? null,\n sourceEntityId: object.sourceEntityId ?? null,\n previewData: object.previewData ?? null,\n }), [object.entityId, object.entityModule, object.entityType, object.sourceEntityId, object.sourceEntityType, object.previewData])\n\n return (\n <>\n {viewHref ? (\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"outline\"\n asChild\n aria-label={t('common.view', 'View')}\n title={t('common.view', 'View')}\n >\n <Link href={viewHref}>\n <ExternalLink className=\"h-4 w-4\" />\n </Link>\n </Button>\n ) : null}\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n disabled={disabled}\n onClick={openComposer}\n aria-label={t('messages.compose', 'Compose message')}\n title={t('messages.compose', 'Compose message')}\n >\n <Send className=\"h-4 w-4\" />\n </Button>\n <MessageComposer\n variant=\"compose\"\n open={open}\n onOpenChange={setOpen}\n lockedType={lockedType}\n contextObject={contextObject}\n requiredActionConfig={requiredActionConfig}\n defaultValues={defaultValues}\n onSuccess={onSuccess}\n />\n </>\n )\n}\n"],
5
+ "mappings": ";AAkDI,mBAWQ,KAXR;AAhDJ,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,cAAc,YAAY;AACnC,SAAS,YAAY;AACrB,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,OAIK;AAYA,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,uBAAuB;AAAA,EACvB,WAAW;AAAA,EACX,WAAW;AAAA,EACX;AACF,GAAiC;AAC/B,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,KAAK;AAE5C,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,QAAI,SAAU;AACd,YAAQ,IAAI;AAAA,EACd,GAAG,CAAC,QAAQ,CAAC;AACb,QAAM,gBAAgB,MAAM,QAAQ,OAAO;AAAA,IACzC,cAAc,OAAO;AAAA,IACrB,YAAY,OAAO;AAAA,IACnB,UAAU,OAAO;AAAA,IACjB,kBAAkB,OAAO,oBAAoB;AAAA,IAC7C,gBAAgB,OAAO,kBAAkB;AAAA,IACzC,aAAa,OAAO,eAAe;AAAA,EACrC,IAAI,CAAC,OAAO,UAAU,OAAO,cAAc,OAAO,YAAY,OAAO,gBAAgB,OAAO,kBAAkB,OAAO,WAAW,CAAC;AAEjI,SACE,iCACG;AAAA,eACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,SAAO;AAAA,QACP,cAAY,EAAE,eAAe,MAAM;AAAA,QACnC,OAAO,EAAE,eAAe,MAAM;AAAA,QAE9B,8BAAC,QAAK,MAAM,UACV,8BAAC,gBAAa,WAAU,WAAU,GACpC;AAAA;AAAA,IACF,IACE;AAAA,IACJ;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,SAAQ;AAAA,QACR;AAAA,QACA,SAAS;AAAA,QACT,cAAY,EAAE,oBAAoB,iBAAiB;AAAA,QACnD,OAAO,EAAE,oBAAoB,iBAAiB;AAAA,QAE9C,8BAAC,QAAK,WAAU,WAAU;AAAA;AAAA,IAC5B;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,SAAQ;AAAA,QACR;AAAA,QACA,cAAc;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -3,8 +3,12 @@ import { SendObjectMessageDialog } from "./SendObjectMessageDialog.js";
3
3
  import { MessageObjectRecordPicker } from "./MessageObjectRecordPicker.js";
4
4
  import { MessagesIcon } from "./MessagesIcon.js";
5
5
  import { useMessagesPoll } from "./useMessagesPoll.js";
6
+ import { MessageObjectPreview } from "./MessageObjectPreview.js";
7
+ import { MessageObjectDetail } from "./MessageObjectDetail.js";
6
8
  export {
7
9
  MessageComposer,
10
+ MessageObjectDetail,
11
+ MessageObjectPreview,
8
12
  MessageObjectRecordPicker,
9
13
  MessagesIcon,
10
14
  SendObjectMessageDialog,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/messages/index.ts"],
4
- "sourcesContent": ["export { MessageComposer } from './MessageComposer'\nexport type {\n MessageComposerContextObject,\n MessageComposerProps,\n MessageComposerVariant,\n MessageTypeItem,\n} from './MessageComposer'\n\nexport { SendObjectMessageDialog } from './SendObjectMessageDialog'\nexport type { SendObjectMessageDialogProps } from './SendObjectMessageDialog'\n\nexport { MessageObjectRecordPicker } from './MessageObjectRecordPicker'\nexport type { MessageObjectRecordPickerProps } from './MessageObjectRecordPicker'\n\nexport { MessagesIcon } from './MessagesIcon'\nexport type { MessagesIconProps } from './MessagesIcon'\n\nexport { useMessagesPoll } from './useMessagesPoll'\nexport type { MessagePollItem, UseMessagesPollResult } from './useMessagesPoll'\n"],
5
- "mappings": "AAAA,SAAS,uBAAuB;AAQhC,SAAS,+BAA+B;AAGxC,SAAS,iCAAiC;AAG1C,SAAS,oBAAoB;AAG7B,SAAS,uBAAuB;",
4
+ "sourcesContent": ["export { MessageComposer } from './MessageComposer'\nexport type {\n MessageComposerContextObject,\n MessageComposerProps,\n MessageComposerVariant,\n MessageTypeItem,\n} from './MessageComposer'\n\nexport { SendObjectMessageDialog } from './SendObjectMessageDialog'\nexport type { SendObjectMessageDialogProps } from './SendObjectMessageDialog'\n\nexport { MessageObjectRecordPicker } from './MessageObjectRecordPicker'\nexport type { MessageObjectRecordPickerProps } from './MessageObjectRecordPicker'\n\nexport { MessagesIcon } from './MessagesIcon'\nexport type { MessagesIconProps } from './MessagesIcon'\n\nexport { useMessagesPoll } from './useMessagesPoll'\nexport type { MessagePollItem, UseMessagesPollResult } from './useMessagesPoll'\n\nexport { MessageObjectPreview } from './MessageObjectPreview'\nexport { MessageObjectDetail } from './MessageObjectDetail'\n"],
5
+ "mappings": "AAAA,SAAS,uBAAuB;AAQhC,SAAS,+BAA+B;AAGxC,SAAS,iCAAiC;AAG1C,SAAS,oBAAoB;AAG7B,SAAS,uBAAuB;AAGhC,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.4.5-develop-610fbb24ec",
3
+ "version": "0.4.5-develop-811deeb983",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -116,7 +116,7 @@
116
116
  }
117
117
  },
118
118
  "dependencies": {
119
- "@open-mercato/shared": "0.4.5-develop-610fbb24ec",
119
+ "@open-mercato/shared": "0.4.5-develop-811deeb983",
120
120
  "@radix-ui/react-popover": "^1.1.6",
121
121
  "@radix-ui/react-tooltip": "^1.2.8",
122
122
  "date-fns": "^4.1.0",
@@ -5,10 +5,12 @@ import { Forward, Reply, Send } from 'lucide-react'
5
5
  import { CrudForm } from '../CrudForm'
6
6
  import { Button } from '../../primitives/button'
7
7
  import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../primitives/dialog'
8
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
8
9
  import { getMessageUiComponentRegistry } from '@open-mercato/core/modules/messages/components/utils/typeUiRegistry'
10
+ import { getMessageObjectType } from '@open-mercato/core/modules/messages/lib/message-objects-registry'
9
11
  import { createMessageComposeFormGroups } from './message-compose-form-groups'
10
12
  import { useMessageCompose } from './useMessageCompose'
11
- import type { MessageComposerProps } from './message-composer.types'
13
+ import type { MessageComposerContextObject, MessageComposerProps } from './message-composer.types'
12
14
 
13
15
  export type {
14
16
  MessageComposerContextObject,
@@ -19,10 +21,47 @@ export type {
19
21
  MessageTypeItem,
20
22
  } from './message-composer.types'
21
23
 
24
+ function ContextObjectPreview({ contextObject }: { contextObject: MessageComposerContextObject }) {
25
+ const t = useT()
26
+ const registry = getMessageUiComponentRegistry()
27
+ const previewComponentKey = `${contextObject.entityModule}:${contextObject.entityType}`
28
+ const PreviewComponent = registry.objectPreviewComponents[previewComponentKey]
29
+ ?? registry.objectPreviewComponents['messages:default']
30
+ const objectType = getMessageObjectType(contextObject.entityModule, contextObject.entityType)
31
+
32
+ if (PreviewComponent) {
33
+ return (
34
+ <PreviewComponent
35
+ entityId={contextObject.entityId}
36
+ entityModule={contextObject.entityModule}
37
+ entityType={contextObject.entityType}
38
+ actionRequired={contextObject.actionRequired}
39
+ actionType={contextObject.actionType}
40
+ actionLabel={contextObject.actionLabel}
41
+ previewData={contextObject.previewData ?? undefined}
42
+ icon={objectType?.icon}
43
+ />
44
+ )
45
+ }
46
+
47
+ return (
48
+ <div className="space-y-1">
49
+ <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
50
+ {t('messages.composer.contextPreview.title', 'Context object')}
51
+ </p>
52
+ <p className="text-sm font-medium">
53
+ {contextObject.entityModule}:{contextObject.entityType}
54
+ </p>
55
+ <p className="text-xs font-mono text-muted-foreground">
56
+ {contextObject.entityId}
57
+ </p>
58
+ </div>
59
+ )
60
+ }
61
+
22
62
  export function MessageComposer(props: MessageComposerProps) {
23
63
  const compose = useMessageCompose(props)
24
64
  const inlineBackHref = props.inlineBackHref
25
- const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])
26
65
  const SubmitIcon = compose.variant === 'reply'
27
66
  ? Reply
28
67
  : compose.variant === 'forward'
@@ -90,38 +129,8 @@ export function MessageComposer(props: MessageComposerProps) {
90
129
  const fallbackContextPreview = React.useMemo(() => {
91
130
  if (compose.contextPreview) return compose.contextPreview
92
131
  if (!props.contextObject) return null
93
-
94
- const previewComponentKey = `${props.contextObject.entityModule}:${props.contextObject.entityType}`
95
- const PreviewComponent = messageUiRegistry.objectPreviewComponents[previewComponentKey]
96
- ?? messageUiRegistry.objectPreviewComponents['messages:default']
97
-
98
- if (PreviewComponent) {
99
- return (
100
- <PreviewComponent
101
- entityId={props.contextObject.entityId}
102
- entityModule={props.contextObject.entityModule}
103
- entityType={props.contextObject.entityType}
104
- actionRequired={props.contextObject.actionRequired}
105
- actionType={props.contextObject.actionType}
106
- actionLabel={props.contextObject.actionLabel}
107
- />
108
- )
109
- }
110
-
111
- return (
112
- <div className="space-y-1">
113
- <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
114
- {compose.t('messages.composer.contextPreview.title', 'Context object')}
115
- </p>
116
- <p className="text-sm font-medium">
117
- {props.contextObject.entityModule}:{props.contextObject.entityType}
118
- </p>
119
- <p className="text-xs font-mono text-muted-foreground">
120
- {props.contextObject.entityId}
121
- </p>
122
- </div>
123
- )
124
- }, [compose.contextPreview, compose.t, messageUiRegistry.objectPreviewComponents, props.contextObject])
132
+ return <ContextObjectPreview contextObject={props.contextObject} />
133
+ }, [compose.contextPreview, props.contextObject])
125
134
 
126
135
  const composeWithContextPreview = React.useMemo(
127
136
  () => ({ ...compose, contextPreview: fallbackContextPreview }),
@@ -0,0 +1,95 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import Link from 'next/link'
5
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
6
+ import type { ObjectDetailProps } from '@open-mercato/shared/modules/messages/types'
7
+ import { Button } from '@open-mercato/ui/primitives/button'
8
+ import { MessageObjectPreview } from './MessageObjectPreview'
9
+
10
+ function resolveActionHref(template: string, entityId: string): string {
11
+ return template.replace('{entityId}', encodeURIComponent(entityId))
12
+ }
13
+
14
+ export function MessageObjectDetail(props: ObjectDetailProps) {
15
+ const t = useT()
16
+ const [executingActionId, setExecutingActionId] = React.useState<string | null>(null)
17
+
18
+ const viewAction = props.actions.find((a) => a.id === 'view')
19
+ const otherActions = props.actions.filter((a) => a.id !== 'view')
20
+
21
+ const preview = (
22
+ <MessageObjectPreview
23
+ entityId={props.entityId}
24
+ entityModule={props.entityModule}
25
+ entityType={props.entityType}
26
+ snapshot={props.snapshot}
27
+ previewData={props.previewData}
28
+ actionRequired={props.actionRequired}
29
+ actionType={props.actionType}
30
+ actionLabel={props.actionLabel}
31
+ icon={props.icon}
32
+ />
33
+ )
34
+
35
+ return (
36
+ <div className="space-y-3 rounded border p-3">
37
+ {viewAction?.href ? (
38
+ <Link
39
+ href={resolveActionHref(viewAction.href, props.entityId)}
40
+ className="block rounded-md transition-opacity hover:opacity-75"
41
+ >
42
+ {preview}
43
+ </Link>
44
+ ) : (
45
+ preview
46
+ )}
47
+
48
+ {otherActions.length > 0 ? (
49
+ <div className="flex flex-wrap gap-2">
50
+ {otherActions.map((action) => {
51
+ if (action.href) {
52
+ return (
53
+ <Button
54
+ key={action.id}
55
+ type="button"
56
+ size="sm"
57
+ variant={action.variant ?? 'default'}
58
+ asChild
59
+ >
60
+ <Link href={resolveActionHref(action.href, props.entityId)}>
61
+ {t(action.labelKey ?? action.id, action.id)}
62
+ </Link>
63
+ </Button>
64
+ )
65
+ }
66
+ return (
67
+ <Button
68
+ key={action.id}
69
+ type="button"
70
+ size="sm"
71
+ variant={action.variant ?? 'default'}
72
+ disabled={executingActionId !== null}
73
+ onClick={async () => {
74
+ if (executingActionId) return
75
+ setExecutingActionId(action.id)
76
+ try {
77
+ await props.onAction(action.id, { id: props.entityId })
78
+ } finally {
79
+ setExecutingActionId(null)
80
+ }
81
+ }}
82
+ >
83
+ {executingActionId === action.id
84
+ ? t('messages.actions.executing', 'Executing...')
85
+ : t(action.labelKey ?? action.id, action.id)}
86
+ </Button>
87
+ )
88
+ })}
89
+ </div>
90
+ ) : null}
91
+ </div>
92
+ )
93
+ }
94
+
95
+ export default MessageObjectDetail
@@ -0,0 +1,64 @@
1
+ "use client"
2
+
3
+ import { Box, type LucideIcon } from 'lucide-react'
4
+ import * as lucideIcons from 'lucide-react'
5
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
6
+ import type { ObjectPreviewProps } from '@open-mercato/shared/modules/messages/types'
7
+ import { Badge } from '@open-mercato/ui/primitives/badge'
8
+
9
+ function resolveIcon(name: string | undefined): LucideIcon {
10
+ if (!name) return Box
11
+ const key = name
12
+ .split('-')
13
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
14
+ .join('') as keyof typeof lucideIcons
15
+ const candidate = lucideIcons[key]
16
+ if (typeof candidate === 'function' || (typeof candidate === 'object' && candidate !== null && '$$typeof' in candidate)) {
17
+ return candidate as LucideIcon
18
+ }
19
+ return Box
20
+ }
21
+
22
+ export function MessageObjectPreview({
23
+ previewData,
24
+ actionRequired,
25
+ actionLabel,
26
+ icon,
27
+ }: ObjectPreviewProps) {
28
+ const t = useT()
29
+ const Icon = resolveIcon(icon)
30
+
31
+ return (
32
+ <div className="flex items-start gap-3 rounded-md border bg-muted/20 p-3">
33
+ <Icon className="mt-0.5 h-4 w-4 text-muted-foreground" />
34
+ <div className="min-w-0 flex-1 space-y-1">
35
+ <div className="flex items-center gap-2">
36
+ <p className="truncate text-sm font-medium">{previewData?.title || ''}</p>
37
+ {actionRequired ? (
38
+ <Badge variant="secondary" className="text-xs">
39
+ {actionLabel || t('messages.composer.objectActionRequired', 'Action required')}
40
+ </Badge>
41
+ ) : null}
42
+ </div>
43
+ {previewData?.subtitle ? (
44
+ <p className="truncate text-xs text-muted-foreground">{previewData.subtitle}</p>
45
+ ) : null}
46
+ {previewData?.status ? (
47
+ <Badge variant="outline" className="text-xs">{previewData.status}</Badge>
48
+ ) : null}
49
+ {previewData?.metadata && Object.keys(previewData.metadata).length > 0 ? (
50
+ <dl className="space-y-1 pt-1">
51
+ {Object.entries(previewData.metadata).map(([key, value]) => (
52
+ <div key={key} className="flex items-start gap-2 text-xs text-muted-foreground">
53
+ <dt className="font-medium capitalize">{key}:</dt>
54
+ <dd className="truncate">{value}</dd>
55
+ </div>
56
+ ))}
57
+ </dl>
58
+ ) : null}
59
+ </div>
60
+ </div>
61
+ )
62
+ }
63
+
64
+ export default MessageObjectPreview
@@ -6,6 +6,7 @@ import { Button } from '../../primitives/button'
6
6
  import { Input } from '../../primitives/input'
7
7
  import { Label } from '../../primitives/label'
8
8
  import { getMessageUiComponentRegistry } from '@open-mercato/core/modules/messages/components/utils/typeUiRegistry'
9
+ import { getMessageObjectType } from '@open-mercato/core/modules/messages/lib/message-objects-registry'
9
10
 
10
11
  export type MessageObjectOptionItem = {
11
12
  id: string
@@ -90,6 +91,9 @@ export function MessageObjectRecordPicker({
90
91
  const PreviewComponent = (componentKey
91
92
  ? messageUiRegistry.objectPreviewComponents[componentKey]
92
93
  : null) ?? messageUiRegistry.objectPreviewComponents['messages:default'] ?? null
94
+ const objectType = resolvedModule && resolvedEntityType
95
+ ? getMessageObjectType(resolvedModule, resolvedEntityType)
96
+ : null
93
97
 
94
98
  return (
95
99
  <div
@@ -111,6 +115,7 @@ export function MessageObjectRecordPicker({
111
115
  title: item.label,
112
116
  subtitle: item.subtitle || undefined,
113
117
  }}
118
+ icon={objectType?.icon}
114
119
  />
115
120
  ) : (
116
121
  <div className="text-sm">
@@ -1,7 +1,8 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { Send } from 'lucide-react'
4
+ import Link from 'next/link'
5
+ import { ExternalLink, Send } from 'lucide-react'
5
6
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
7
  import { Button } from '../../primitives/button'
7
8
  import {
@@ -17,10 +18,8 @@ export type SendObjectMessageDialogProps = {
17
18
  lockedType?: string | null
18
19
  requiredActionConfig?: MessageComposerRequiredActionConfig | null
19
20
  disabled?: boolean
20
- contextPreview?: React.ReactNode
21
- children?: React.ReactNode
21
+ viewHref?: string | null
22
22
  onSuccess?: MessageComposerProps['onSuccess']
23
- renderTrigger?: (params: { openComposer: () => void; disabled: boolean }) => React.ReactNode
24
23
  }
25
24
 
26
25
  export function SendObjectMessageDialog({
@@ -29,10 +28,8 @@ export function SendObjectMessageDialog({
29
28
  lockedType = 'messages.defaultWithObjects',
30
29
  requiredActionConfig = null,
31
30
  disabled = false,
32
- contextPreview = null,
33
- children = null,
31
+ viewHref = null,
34
32
  onSuccess,
35
- renderTrigger,
36
33
  }: SendObjectMessageDialogProps) {
37
34
  const t = useT()
38
35
  const [open, setOpen] = React.useState(false)
@@ -47,15 +44,29 @@ export function SendObjectMessageDialog({
47
44
  entityId: object.entityId,
48
45
  sourceEntityType: object.sourceEntityType ?? null,
49
46
  sourceEntityId: object.sourceEntityId ?? null,
50
- }), [object.entityId, object.entityModule, object.entityType, object.sourceEntityId, object.sourceEntityType])
47
+ previewData: object.previewData ?? null,
48
+ }), [object.entityId, object.entityModule, object.entityType, object.sourceEntityId, object.sourceEntityType, object.previewData])
51
49
 
52
- const trigger = renderTrigger
53
- ? renderTrigger({ openComposer, disabled })
54
- : (
50
+ return (
51
+ <>
52
+ {viewHref ? (
53
+ <Button
54
+ type="button"
55
+ size="icon"
56
+ variant="outline"
57
+ asChild
58
+ aria-label={t('common.view', 'View')}
59
+ title={t('common.view', 'View')}
60
+ >
61
+ <Link href={viewHref}>
62
+ <ExternalLink className="h-4 w-4" />
63
+ </Link>
64
+ </Button>
65
+ ) : null}
55
66
  <Button
56
67
  type="button"
57
68
  size="icon"
58
- variant="outline"
69
+ variant="ghost"
59
70
  disabled={disabled}
60
71
  onClick={openComposer}
61
72
  aria-label={t('messages.compose', 'Compose message')}
@@ -63,11 +74,6 @@ export function SendObjectMessageDialog({
63
74
  >
64
75
  <Send className="h-4 w-4" />
65
76
  </Button>
66
- )
67
-
68
- return (
69
- <>
70
- {trigger}
71
77
  <MessageComposer
72
78
  variant="compose"
73
79
  open={open}
@@ -75,7 +81,6 @@ export function SendObjectMessageDialog({
75
81
  lockedType={lockedType}
76
82
  contextObject={contextObject}
77
83
  requiredActionConfig={requiredActionConfig}
78
- contextPreview={contextPreview ?? children}
79
84
  defaultValues={defaultValues}
80
85
  onSuccess={onSuccess}
81
86
  />
@@ -17,3 +17,6 @@ export type { MessagesIconProps } from './MessagesIcon'
17
17
 
18
18
  export { useMessagesPoll } from './useMessagesPoll'
19
19
  export type { MessagePollItem, UseMessagesPollResult } from './useMessagesPoll'
20
+
21
+ export { MessageObjectPreview } from './MessageObjectPreview'
22
+ export { MessageObjectDetail } from './MessageObjectDetail'
@@ -1,4 +1,5 @@
1
1
  import type * as React from 'react'
2
+ import type { ObjectPreviewData } from '@open-mercato/shared/modules/messages/types'
2
3
  import type { MessagePriority } from './message-priority'
3
4
 
4
5
  export type MessageTypeItem = {
@@ -34,6 +35,7 @@ export type MessageComposerContextObject = {
34
35
  actionLabel?: string
35
36
  sourceEntityType?: string | null
36
37
  sourceEntityId?: string | null
38
+ previewData?: ObjectPreviewData | null
37
39
  }
38
40
 
39
41
  export type MessageComposerRequiredActionOption = {