@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.
- package/dist/backend/messages/MessageComposer.js +35 -26
- package/dist/backend/messages/MessageComposer.js.map +2 -2
- package/dist/backend/messages/MessageObjectDetail.js +81 -0
- package/dist/backend/messages/MessageObjectDetail.js.map +7 -0
- package/dist/backend/messages/MessageObjectPreview.js +48 -0
- package/dist/backend/messages/MessageObjectPreview.js.map +7 -0
- package/dist/backend/messages/MessageObjectRecordPicker.js +4 -1
- package/dist/backend/messages/MessageObjectRecordPicker.js.map +2 -2
- package/dist/backend/messages/SendObjectMessageDialog.js +32 -22
- package/dist/backend/messages/SendObjectMessageDialog.js.map +2 -2
- package/dist/backend/messages/index.js +4 -0
- package/dist/backend/messages/index.js.map +2 -2
- package/package.json +2 -2
- package/src/backend/messages/MessageComposer.tsx +43 -34
- package/src/backend/messages/MessageObjectDetail.tsx +95 -0
- package/src/backend/messages/MessageObjectPreview.tsx +64 -0
- package/src/backend/messages/MessageObjectRecordPicker.tsx +5 -0
- package/src/backend/messages/SendObjectMessageDialog.tsx +23 -18
- package/src/backend/messages/index.ts +3 -0
- package/src/backend/messages/message-composer.types.ts +2 -0
|
@@ -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
|
-
|
|
65
|
-
|
|
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\
|
|
5
|
-
"mappings": ";AAiCM,
|
|
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": ";
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
|
5
|
-
"mappings": ";
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
previewData: object.previewData ?? null,
|
|
48
|
+
}), [object.entityId, object.entityModule, object.entityType, object.sourceEntityId, object.sourceEntityType, object.previewData])
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
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="
|
|
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 = {
|