@open-mercato/core 0.6.8-develop.7023.1.a1960eb208 → 0.6.8-develop.7026.1.23445b3f3e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/roles/acl/route.js +28 -33
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +96 -51
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/commands/acl.js +254 -0
- package/dist/modules/auth/commands/acl.js.map +7 -0
- package/dist/modules/auth/lib/grantChecks.js.map +2 -2
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js +9 -1
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +2 -2
- package/dist/modules/inbox_ops/lib/htmlToPlainText.js +1 -12
- package/dist/modules/inbox_ops/lib/htmlToPlainText.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +3 -0
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/messages/api/openapi.js +3 -0
- package/dist/modules/messages/api/openapi.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +3 -13
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/components/inboxFilters.js +15 -0
- package/dist/modules/messages/components/inboxFilters.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js +11 -1
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js.map +2 -2
- package/dist/modules/messages/components/message-detail/panels/MessageHeader.js +3 -1
- package/dist/modules/messages/components/message-detail/panels/MessageHeader.js.map +2 -2
- package/dist/modules/messages/components/message-detail/panels/thread-panel.js +2 -1
- package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +2 -2
- package/dist/modules/messages/components/messageListLabels.js +14 -2
- package/dist/modules/messages/components/messageListLabels.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +64 -14
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/package.json +7 -9
- package/src/modules/auth/api/roles/acl/route.ts +42 -39
- package/src/modules/auth/api/users/acl/route.ts +177 -53
- package/src/modules/auth/commands/acl.ts +495 -0
- package/src/modules/auth/i18n/de.json +2 -0
- package/src/modules/auth/i18n/en.json +2 -0
- package/src/modules/auth/i18n/es.json +2 -0
- package/src/modules/auth/i18n/ko.json +2 -0
- package/src/modules/auth/i18n/pl.json +2 -0
- package/src/modules/auth/lib/grantChecks.ts +6 -0
- package/src/modules/communication_channels/commands/ingest-inbound-message.ts +33 -1
- package/src/modules/inbox_ops/lib/htmlToPlainText.ts +1 -17
- package/src/modules/messages/api/[id]/route.ts +3 -0
- package/src/modules/messages/api/openapi.ts +3 -0
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +5 -16
- package/src/modules/messages/components/inboxFilters.ts +36 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.ts +14 -1
- package/src/modules/messages/components/message-detail/panels/MessageHeader.tsx +3 -1
- package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +2 -1
- package/src/modules/messages/components/message-detail/types.ts +3 -0
- package/src/modules/messages/components/messageListLabels.ts +52 -3
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +101 -14
- package/src/modules/sales/i18n/de.json +2 -0
- package/src/modules/sales/i18n/en.json +2 -0
- package/src/modules/sales/i18n/es.json +2 -0
- package/src/modules/sales/i18n/ko.json +2 -0
- package/src/modules/sales/i18n/pl.json +2 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/components/MessagesInboxPageClient.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/messages/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'\nimport { getMessageUiComponentRegistry } from './utils/typeUiRegistry'\nimport { DefaultMessageListItem } from './defaults/DefaultMessageListItem'\nimport { getMessageListParticipantLabel } from './messageListLabels'\nimport { toErrorMessage } from './message-detail/utils'\nimport { useMessagesInboxBulkActions, type MessageFolder } from './useMessagesInboxBulkActions'\nimport {\n buildMessagesInboxFilters,\n buildMessagesListParams,\n type SenderOption,\n} from './inboxFilters'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('messages').child({ component: 'MessagesInboxPageClient' })\n\ntype MessageListItem = {\n id: string\n type: string\n subject: string\n bodyPreview: string\n senderUserId: string\n senderName?: string | null\n senderEmail?: string | null\n priority: string\n status: string\n hasObjects: boolean\n objectCount: number\n hasAttachments: boolean\n attachmentCount: number\n recipientCount?: number\n hasActions: boolean\n actionTaken?: string | null\n sentAt?: string | null\n readAt?: string | null\n threadId?: string | null\n}\n\ntype MessageListResponse = {\n items?: MessageListItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n}\n\ntype MessageTypeItem = {\n type: string\n module: string\n labelKey: string\n ui?: {\n listItemComponent?: string | null\n } | null\n}\n\ntype UserListItem = {\n id: string\n email?: string | null\n name?: string | null\n}\n\nexport function MessagesInboxPageClient() {\n const router = useRouter()\n const t = useT()\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n\n const invalidateMessageListQueries = React.useCallback(() => {\n void queryClient.invalidateQueries({ queryKey: ['messages', 'list'] })\n }, [queryClient])\n\n useAppEvent('messages.message.*', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n useAppEvent('om:bridge:reconnected', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n const [folder, setFolder] = React.useState<MessageFolder>('inbox')\n const [folderMenuOpen, setFolderMenuOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [page, setPage] = React.useState(1)\n const pageSize = 20\n const folderMenuRef = React.useRef<HTMLDivElement | null>(null)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n const { bulkActions, selectionScopeKey, injectionContext, ConfirmDialogElement } = useMessagesInboxBulkActions<MessageListItem>({\n folder,\n page,\n search,\n filterValues,\n })\n\n const listQuery = useQuery({\n queryKey: [\n 'messages',\n 'list',\n folder,\n search,\n page,\n pageSize,\n JSON.stringify(filterValues),\n scopeVersion,\n ],\n queryFn: async () => {\n const params = buildMessagesListParams({\n folder,\n page,\n pageSize,\n search,\n filterValues,\n })\n\n const call = await apiCall<MessageListResponse>(`/api/messages?${params.toString()}`)\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadListFailed', 'Failed to load messages.'),\n )\n }\n\n return {\n items: Array.isArray(call.result?.items) ? call.result?.items ?? [] : [],\n total: Number(call.result?.total ?? 0),\n page: Number(call.result?.page ?? page),\n pageSize: Number(call.result?.pageSize ?? pageSize),\n totalPages: Number(call.result?.totalPages ?? 0),\n }\n },\n })\n\n const messageTypesQuery = useQuery({\n queryKey: ['messages', 'types', scopeVersion],\n queryFn: async () => {\n const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n )\n }\n return Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n },\n })\n\n React.useEffect(() => {\n if (!listQuery.error) return\n flash(\n listQuery.error instanceof Error\n ? listQuery.error.message\n : t('messages.errors.loadListFailed', 'Failed to load messages.'),\n 'error',\n )\n }, [listQuery.error, t])\n\n React.useEffect(() => {\n if (!messageTypesQuery.error) return\n flash(\n messageTypesQuery.error instanceof Error\n ? messageTypesQuery.error.message\n : t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n 'error',\n )\n }, [messageTypesQuery.error, t])\n\n const messageTypeLabelMap = React.useMemo(() => {\n const map: Record<string, string> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = t(item.labelKey, item.type)\n }\n return map\n }, [messageTypesQuery.data, t])\n\n const [senderOptions, setSenderOptions] = React.useState<SenderOption[]>([])\n const senderOptionsScopeRef = React.useRef(scopeVersion)\n\n const mergeSenderOptions = React.useCallback((incoming: SenderOption[]) => {\n if (incoming.length === 0) return\n setSenderOptions((prev) => {\n const map = new Map<string, SenderOption>()\n for (const opt of prev) map.set(opt.value, opt)\n for (const opt of incoming) map.set(opt.value, opt)\n return Array.from(map.values())\n })\n }, [])\n\n const loadSenderOptions = React.useCallback(async (query?: string) => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '20')\n if (query && query.trim().length > 0) {\n params.set('search', query.trim())\n }\n\n const call = await apiCall<{ items?: UserListItem[] }>(\n `/api/auth/users?${params.toString()}`,\n {\n headers: {\n 'x-om-forbidden-redirect': '0',\n },\n },\n ).catch(() => null)\n if (!call) return []\n if (!call.ok) return []\n\n const items = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n const next: SenderOption[] = items.flatMap((item) => {\n if (!item || typeof item.id !== 'string' || item.id.trim().length === 0) return []\n const name = typeof item.name === 'string' && item.name.trim().length > 0 ? item.name.trim() : null\n const email = typeof item.email === 'string' && item.email.trim().length > 0 ? item.email.trim() : null\n const label = name ?? email ?? item.id\n return [{\n value: item.id,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n if (senderOptionsScopeRef.current === scopeVersion) {\n mergeSenderOptions(next)\n }\n return next\n }, [mergeSenderOptions, scopeVersion])\n\n React.useEffect(() => {\n const items = listQuery.data?.items ?? []\n const next = items.flatMap((item): SenderOption[] => {\n if (typeof item.senderUserId !== 'string' || item.senderUserId.trim().length === 0) return []\n const name = typeof item.senderName === 'string' && item.senderName.trim().length > 0\n ? item.senderName.trim()\n : null\n const email = typeof item.senderEmail === 'string' && item.senderEmail.trim().length > 0\n ? item.senderEmail.trim()\n : null\n const label = name ?? email ?? item.senderUserId\n return [{\n value: item.senderUserId,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n mergeSenderOptions(next)\n }, [listQuery.data?.items, mergeSenderOptions])\n\n React.useEffect(() => {\n senderOptionsScopeRef.current = scopeVersion\n setSenderOptions([])\n loadSenderOptions().catch((error: unknown) => {\n logger.warn('Failed to load sender filter options', { err: error })\n })\n }, [loadSenderOptions, scopeVersion])\n\n const listItemComponentKeyByType = React.useMemo(() => {\n const map: Record<string, string | null> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = item.ui?.listItemComponent ?? null\n }\n return map\n }, [messageTypesQuery.data])\n\n const filters = React.useMemo<FilterDef[]>(() => {\n const typeOptions = (messageTypesQuery.data ?? []).map((item) => ({\n value: item.type,\n label: t(item.labelKey, item.type),\n }))\n\n return buildMessagesInboxFilters({\n t,\n typeOptions,\n senderOptions,\n loadSenderOptions,\n })\n }, [loadSenderOptions, messageTypesQuery.data, senderOptions, t])\n\n const columns = React.useMemo<ColumnDef<MessageListItem>[]>(() => [\n {\n accessorKey: 'message',\n header: t('messages.title', 'Messages'),\n meta: {\n truncate: false,\n maxWidth: '100%',\n },\n cell: ({ row }) => {\n const item = row.original\n const listItemComponentKey = listItemComponentKeyByType[item.type]\n const ListItemComponent = listItemComponentKey\n ? messageUiRegistry.listItemComponents[listItemComponentKey] ?? null\n : null\n const ComponentToUse = ListItemComponent || DefaultMessageListItem\n\n return (\n <ComponentToUse\n message={{\n id: item.id,\n type: item.type,\n typeLabel: messageTypeLabelMap[item.type] ?? item.type,\n subject: item.subject,\n body: item.bodyPreview,\n bodyFormat: 'text' as const,\n priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',\n sentAt: item.sentAt ? new Date(item.sentAt) : null,\n senderName: getMessageListParticipantLabel(item, folder, t),\n hasObjects: item.hasObjects,\n objectCount: item.objectCount,\n hasAttachments: item.hasAttachments,\n attachmentCount: item.attachmentCount,\n recipientCount: item.recipientCount ?? 0,\n hasActions: item.hasActions,\n actionTaken: item.actionTaken ?? null,\n unread: item.status === 'unread',\n }}\n onClick={() => router.push(`/backend/messages/${item.id}`)}\n />\n )\n },\n },\n ], [folder, listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])\n\n const folderOptions = React.useMemo(() => [\n { id: 'inbox' as const, label: t('messages.folder.inbox', 'Inbox'), icon: Inbox },\n { id: 'sent' as const, label: t('messages.folder.sent', 'Sent'), icon: Send },\n { id: 'drafts' as const, label: t('messages.folder.drafts', 'Drafts'), icon: FilePenLine },\n { id: 'archived' as const, label: t('messages.folder.archived', 'Archived'), icon: Archive },\n { id: 'all' as const, label: t('messages.folder.all', 'All'), icon: Layers },\n ], [t])\n\n const activeFolderOption = folderOptions.find((option) => option.id === folder) ?? folderOptions[0]\n const ActiveFolderIcon = activeFolderOption.icon\n\n React.useEffect(() => {\n if (!folderMenuOpen) return\n const handleClickOutside = (event: MouseEvent) => {\n if (!folderMenuRef.current) return\n const target = event.target\n if (target instanceof Node && !folderMenuRef.current.contains(target)) {\n setFolderMenuOpen(false)\n }\n }\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setFolderMenuOpen(false)\n }\n document.addEventListener('mousedown', handleClickOutside)\n document.addEventListener('keydown', handleEscape)\n return () => {\n document.removeEventListener('mousedown', handleClickOutside)\n document.removeEventListener('keydown', handleEscape)\n }\n }, [folderMenuOpen])\n\n const rows = listQuery.data?.items ?? []\n const total = listQuery.data?.total ?? 0\n const totalPages = listQuery.data?.totalPages ?? 0\n\n return (\n <div className=\"space-y-4\">\n <DataTable\n title={t('messages.title', 'Messages')}\n // UMES extension surface \u2014 opt into widget injection at:\n // data-table:messages:columns / :row-actions / :bulk-actions / :filters / :toolbar / :search-trailing\n // (SPEC-045d \u00A79.3a \u2014 communication_channels hub renders channel badge + delivery status here)\n extensionTableId={extensionPoints.hosts.inboxTable.tableId}\n columns={columns}\n data={rows}\n bulkActions={bulkActions}\n selectionScopeKey={selectionScopeKey}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('messages.searchPlaceholder', 'Search messages')}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(value) => {\n setFilterValues(value)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n injectionContext={injectionContext}\n isLoading={listQuery.isLoading || listQuery.isFetching}\n pagination={{\n page,\n pageSize,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"relative\" ref={folderMenuRef}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-2\"\n aria-expanded={folderMenuOpen}\n aria-haspopup=\"menu\"\n onClick={() => setFolderMenuOpen((value) => !value)}\n >\n <ActiveFolderIcon className=\"h-4 w-4\" aria-hidden />\n <span>{t('messages.folder.selector', 'Folder')}:</span>\n <span>{activeFolderOption.label}</span>\n <ChevronDown className=\"h-4 w-4 opacity-70\" aria-hidden />\n </Button>\n {folderMenuOpen ? (\n <div\n className=\"absolute right-0 z-dropdown mt-1 min-w-52 rounded-md border bg-background p-1 shadow\"\n role=\"menu\"\n >\n {folderOptions.map((option) => {\n const Icon = option.icon\n const isActive = option.id === folder\n return (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"menuitemradio\"\n aria-checked={isActive}\n className={`w-full justify-start h-auto px-2 py-1.5 text-sm font-normal ${isActive ? 'bg-accent/60' : ''}`}\n onClick={() => {\n setFolder(option.id)\n setPage(1)\n setFolderMenuOpen(false)\n }}\n >\n <Icon className=\"h-4 w-4\" aria-hidden />\n <span>{option.label}</span>\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n <Button asChild>\n <Link href=\"/backend/messages/compose\">{t('messages.compose', 'Compose message')}</Link>\n </Button>\n </div>\n }\n onRowClick={(row) => {\n router.push(`/backend/messages/${row.id}`)\n }}\n emptyState={(\n <ListEmptyState\n entityName={t('messages.title', 'Messages')}\n createHref=\"/backend/messages/compose\"\n createLabel={t('messages.compose', 'Compose message')}\n />\n )}\n embedded\n />\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/messages/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'\nimport { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'\nimport { getMessageUiComponentRegistry } from './utils/typeUiRegistry'\nimport { DefaultMessageListItem } from './defaults/DefaultMessageListItem'\nimport { getMessageListParticipantLabel } from './messageListLabels'\nimport { toErrorMessage } from './message-detail/utils'\nimport { useMessagesInboxBulkActions, type MessageFolder } from './useMessagesInboxBulkActions'\nimport {\n buildMessagesInboxFilters,\n buildMessagesListParams,\n buildSenderOptionsFromMessages,\n type SenderOption,\n} from './inboxFilters'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('messages').child({ component: 'MessagesInboxPageClient' })\n\ntype MessageListItem = {\n id: string\n type: string\n subject: string\n bodyPreview: string\n senderUserId: string\n senderName?: string | null\n senderEmail?: string | null\n externalName?: string | null\n externalEmail?: string | null\n sourceEntityType?: string | null\n priority: string\n status: string\n hasObjects: boolean\n objectCount: number\n hasAttachments: boolean\n attachmentCount: number\n recipientCount?: number\n hasActions: boolean\n actionTaken?: string | null\n sentAt?: string | null\n readAt?: string | null\n threadId?: string | null\n}\n\ntype MessageListResponse = {\n items?: MessageListItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n}\n\ntype MessageTypeItem = {\n type: string\n module: string\n labelKey: string\n ui?: {\n listItemComponent?: string | null\n } | null\n}\n\ntype UserListItem = {\n id: string\n email?: string | null\n name?: string | null\n}\n\nexport function MessagesInboxPageClient() {\n const router = useRouter()\n const t = useT()\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n\n const invalidateMessageListQueries = React.useCallback(() => {\n void queryClient.invalidateQueries({ queryKey: ['messages', 'list'] })\n }, [queryClient])\n\n useAppEvent('messages.message.*', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n useAppEvent('om:bridge:reconnected', invalidateMessageListQueries, [invalidateMessageListQueries])\n\n const [folder, setFolder] = React.useState<MessageFolder>('inbox')\n const [folderMenuOpen, setFolderMenuOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [page, setPage] = React.useState(1)\n const pageSize = 20\n const folderMenuRef = React.useRef<HTMLDivElement | null>(null)\n const messageUiRegistry = React.useMemo(() => getMessageUiComponentRegistry(), [])\n const { bulkActions, selectionScopeKey, injectionContext, ConfirmDialogElement } = useMessagesInboxBulkActions<MessageListItem>({\n folder,\n page,\n search,\n filterValues,\n })\n\n const listQuery = useQuery({\n queryKey: [\n 'messages',\n 'list',\n folder,\n search,\n page,\n pageSize,\n JSON.stringify(filterValues),\n scopeVersion,\n ],\n queryFn: async () => {\n const params = buildMessagesListParams({\n folder,\n page,\n pageSize,\n search,\n filterValues,\n })\n\n const call = await apiCall<MessageListResponse>(`/api/messages?${params.toString()}`)\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadListFailed', 'Failed to load messages.'),\n )\n }\n\n return {\n items: Array.isArray(call.result?.items) ? call.result?.items ?? [] : [],\n total: Number(call.result?.total ?? 0),\n page: Number(call.result?.page ?? page),\n pageSize: Number(call.result?.pageSize ?? pageSize),\n totalPages: Number(call.result?.totalPages ?? 0),\n }\n },\n })\n\n const messageTypesQuery = useQuery({\n queryKey: ['messages', 'types', scopeVersion],\n queryFn: async () => {\n const call = await apiCall<{ items?: MessageTypeItem[] }>('/api/messages/types')\n if (!call.ok) {\n throw new Error(\n toErrorMessage(call.result)\n ?? t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n )\n }\n return Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n },\n })\n\n React.useEffect(() => {\n if (!listQuery.error) return\n flash(\n listQuery.error instanceof Error\n ? listQuery.error.message\n : t('messages.errors.loadListFailed', 'Failed to load messages.'),\n 'error',\n )\n }, [listQuery.error, t])\n\n React.useEffect(() => {\n if (!messageTypesQuery.error) return\n flash(\n messageTypesQuery.error instanceof Error\n ? messageTypesQuery.error.message\n : t('messages.errors.loadTypesFailed', 'Failed to load message types.'),\n 'error',\n )\n }, [messageTypesQuery.error, t])\n\n const messageTypeLabelMap = React.useMemo(() => {\n const map: Record<string, string> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = t(item.labelKey, item.type)\n }\n return map\n }, [messageTypesQuery.data, t])\n\n const [senderOptions, setSenderOptions] = React.useState<SenderOption[]>([])\n const senderOptionsScopeRef = React.useRef(scopeVersion)\n\n const mergeSenderOptions = React.useCallback((incoming: SenderOption[]) => {\n if (incoming.length === 0) return\n setSenderOptions((prev) => {\n const map = new Map<string, SenderOption>()\n for (const opt of prev) map.set(opt.value, opt)\n for (const opt of incoming) map.set(opt.value, opt)\n return Array.from(map.values())\n })\n }, [])\n\n const loadSenderOptions = React.useCallback(async (query?: string) => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '20')\n if (query && query.trim().length > 0) {\n params.set('search', query.trim())\n }\n\n const call = await apiCall<{ items?: UserListItem[] }>(\n `/api/auth/users?${params.toString()}`,\n {\n headers: {\n 'x-om-forbidden-redirect': '0',\n },\n },\n ).catch(() => null)\n if (!call) return []\n if (!call.ok) return []\n\n const items = Array.isArray(call.result?.items) ? call.result?.items ?? [] : []\n const next: SenderOption[] = items.flatMap((item) => {\n if (!item || typeof item.id !== 'string' || item.id.trim().length === 0) return []\n const name = typeof item.name === 'string' && item.name.trim().length > 0 ? item.name.trim() : null\n const email = typeof item.email === 'string' && item.email.trim().length > 0 ? item.email.trim() : null\n const label = name ?? email ?? item.id\n return [{\n value: item.id,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n if (senderOptionsScopeRef.current === scopeVersion) {\n mergeSenderOptions(next)\n }\n return next\n }, [mergeSenderOptions, scopeVersion])\n\n React.useEffect(() => {\n const items = listQuery.data?.items ?? []\n mergeSenderOptions(buildSenderOptionsFromMessages(items))\n }, [listQuery.data?.items, mergeSenderOptions])\n\n React.useEffect(() => {\n senderOptionsScopeRef.current = scopeVersion\n setSenderOptions([])\n loadSenderOptions().catch((error: unknown) => {\n logger.warn('Failed to load sender filter options', { err: error })\n })\n }, [loadSenderOptions, scopeVersion])\n\n const listItemComponentKeyByType = React.useMemo(() => {\n const map: Record<string, string | null> = {}\n for (const item of messageTypesQuery.data ?? []) {\n map[item.type] = item.ui?.listItemComponent ?? null\n }\n return map\n }, [messageTypesQuery.data])\n\n const filters = React.useMemo<FilterDef[]>(() => {\n const typeOptions = (messageTypesQuery.data ?? []).map((item) => ({\n value: item.type,\n label: t(item.labelKey, item.type),\n }))\n\n return buildMessagesInboxFilters({\n t,\n typeOptions,\n senderOptions,\n loadSenderOptions,\n })\n }, [loadSenderOptions, messageTypesQuery.data, senderOptions, t])\n\n const columns = React.useMemo<ColumnDef<MessageListItem>[]>(() => [\n {\n accessorKey: 'message',\n header: t('messages.title', 'Messages'),\n meta: {\n truncate: false,\n maxWidth: '100%',\n },\n cell: ({ row }) => {\n const item = row.original\n const listItemComponentKey = listItemComponentKeyByType[item.type]\n const ListItemComponent = listItemComponentKey\n ? messageUiRegistry.listItemComponents[listItemComponentKey] ?? null\n : null\n const ComponentToUse = ListItemComponent || DefaultMessageListItem\n\n return (\n <ComponentToUse\n message={{\n id: item.id,\n type: item.type,\n typeLabel: messageTypeLabelMap[item.type] ?? item.type,\n subject: item.subject,\n body: item.bodyPreview,\n bodyFormat: 'text' as const,\n priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',\n sentAt: item.sentAt ? new Date(item.sentAt) : null,\n senderName: getMessageListParticipantLabel(item, folder, t),\n hasObjects: item.hasObjects,\n objectCount: item.objectCount,\n hasAttachments: item.hasAttachments,\n attachmentCount: item.attachmentCount,\n recipientCount: item.recipientCount ?? 0,\n hasActions: item.hasActions,\n actionTaken: item.actionTaken ?? null,\n unread: item.status === 'unread',\n }}\n onClick={() => router.push(`/backend/messages/${item.id}`)}\n />\n )\n },\n },\n ], [folder, listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])\n\n const folderOptions = React.useMemo(() => [\n { id: 'inbox' as const, label: t('messages.folder.inbox', 'Inbox'), icon: Inbox },\n { id: 'sent' as const, label: t('messages.folder.sent', 'Sent'), icon: Send },\n { id: 'drafts' as const, label: t('messages.folder.drafts', 'Drafts'), icon: FilePenLine },\n { id: 'archived' as const, label: t('messages.folder.archived', 'Archived'), icon: Archive },\n { id: 'all' as const, label: t('messages.folder.all', 'All'), icon: Layers },\n ], [t])\n\n const activeFolderOption = folderOptions.find((option) => option.id === folder) ?? folderOptions[0]\n const ActiveFolderIcon = activeFolderOption.icon\n\n React.useEffect(() => {\n if (!folderMenuOpen) return\n const handleClickOutside = (event: MouseEvent) => {\n if (!folderMenuRef.current) return\n const target = event.target\n if (target instanceof Node && !folderMenuRef.current.contains(target)) {\n setFolderMenuOpen(false)\n }\n }\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') setFolderMenuOpen(false)\n }\n document.addEventListener('mousedown', handleClickOutside)\n document.addEventListener('keydown', handleEscape)\n return () => {\n document.removeEventListener('mousedown', handleClickOutside)\n document.removeEventListener('keydown', handleEscape)\n }\n }, [folderMenuOpen])\n\n const rows = listQuery.data?.items ?? []\n const total = listQuery.data?.total ?? 0\n const totalPages = listQuery.data?.totalPages ?? 0\n\n return (\n <div className=\"space-y-4\">\n <DataTable\n title={t('messages.title', 'Messages')}\n // UMES extension surface \u2014 opt into widget injection at:\n // data-table:messages:columns / :row-actions / :bulk-actions / :filters / :toolbar / :search-trailing\n // (SPEC-045d \u00A79.3a \u2014 communication_channels hub renders channel badge + delivery status here)\n extensionTableId={extensionPoints.hosts.inboxTable.tableId}\n columns={columns}\n data={rows}\n bulkActions={bulkActions}\n selectionScopeKey={selectionScopeKey}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={t('messages.searchPlaceholder', 'Search messages')}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(value) => {\n setFilterValues(value)\n setPage(1)\n }}\n onFiltersClear={() => {\n setFilterValues({})\n setPage(1)\n }}\n injectionContext={injectionContext}\n isLoading={listQuery.isLoading || listQuery.isFetching}\n pagination={{\n page,\n pageSize,\n total,\n totalPages,\n onPageChange: setPage,\n }}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"relative\" ref={folderMenuRef}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-2\"\n aria-expanded={folderMenuOpen}\n aria-haspopup=\"menu\"\n onClick={() => setFolderMenuOpen((value) => !value)}\n >\n <ActiveFolderIcon className=\"h-4 w-4\" aria-hidden />\n <span>{t('messages.folder.selector', 'Folder')}:</span>\n <span>{activeFolderOption.label}</span>\n <ChevronDown className=\"h-4 w-4 opacity-70\" aria-hidden />\n </Button>\n {folderMenuOpen ? (\n <div\n className=\"absolute right-0 z-dropdown mt-1 min-w-52 rounded-md border bg-background p-1 shadow\"\n role=\"menu\"\n >\n {folderOptions.map((option) => {\n const Icon = option.icon\n const isActive = option.id === folder\n return (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"menuitemradio\"\n aria-checked={isActive}\n className={`w-full justify-start h-auto px-2 py-1.5 text-sm font-normal ${isActive ? 'bg-accent/60' : ''}`}\n onClick={() => {\n setFolder(option.id)\n setPage(1)\n setFolderMenuOpen(false)\n }}\n >\n <Icon className=\"h-4 w-4\" aria-hidden />\n <span>{option.label}</span>\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n <Button asChild>\n <Link href=\"/backend/messages/compose\">{t('messages.compose', 'Compose message')}</Link>\n </Button>\n </div>\n }\n onRowClick={(row) => {\n router.push(`/backend/messages/${row.id}`)\n }}\n emptyState={(\n <ListEmptyState\n entityName={t('messages.title', 'Messages')}\n createHref=\"/backend/messages/compose\"\n createLabel={t('messages.compose', 'Compose message')}\n />\n )}\n embedded\n />\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
function buildSenderOptionsFromMessages(items) {
|
|
2
|
+
return items.flatMap((item) => {
|
|
3
|
+
if (typeof item.senderUserId !== "string" || item.senderUserId.trim().length === 0) return [];
|
|
4
|
+
const name = typeof item.senderName === "string" && item.senderName.trim().length > 0 ? item.senderName.trim() : null;
|
|
5
|
+
const email = typeof item.senderEmail === "string" && item.senderEmail.trim().length > 0 ? item.senderEmail.trim() : null;
|
|
6
|
+
const label = name ?? email;
|
|
7
|
+
if (!label) return [];
|
|
8
|
+
return [{
|
|
9
|
+
value: item.senderUserId,
|
|
10
|
+
label,
|
|
11
|
+
description: email && email !== label ? email : null
|
|
12
|
+
}];
|
|
13
|
+
});
|
|
14
|
+
}
|
|
1
15
|
function buildMessagesInboxFilters({
|
|
2
16
|
t,
|
|
3
17
|
typeOptions,
|
|
@@ -130,6 +144,7 @@ function buildMessagesListParams({
|
|
|
130
144
|
export {
|
|
131
145
|
buildMessagesInboxFilters,
|
|
132
146
|
buildMessagesListParams,
|
|
147
|
+
buildSenderOptionsFromMessages,
|
|
133
148
|
normalizeMessagesSinceValue
|
|
134
149
|
};
|
|
135
150
|
//# sourceMappingURL=inboxFilters.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/components/inboxFilters.ts"],
|
|
4
|
-
"sourcesContent": ["import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\n\ntype Translator = (key: string, fallback?: string) => string\n\nexport type SenderOption = {\n value: string\n label: string\n description?: string | null\n}\n\nexport type MessagesInboxFilterContext = {\n t: Translator\n typeOptions: { value: string; label: string }[]\n senderOptions: SenderOption[]\n loadSenderOptions: (query?: string) => Promise<SenderOption[]>\n}\n\nexport function buildMessagesInboxFilters({\n t,\n typeOptions,\n senderOptions,\n loadSenderOptions,\n}: MessagesInboxFilterContext): FilterDef[] {\n const senderLabel = (val: string): string => {\n const match = senderOptions.find((opt) => opt.value === val)\n return match?.label ?? val\n }\n\n return [\n {\n id: 'status',\n label: t('messages.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'unread', label: t('messages.status.unread', 'Unread') },\n { value: 'read', label: t('messages.status.read', 'Read') },\n { value: 'archived', label: t('messages.status.archived', 'Archived') },\n ],\n },\n {\n id: 'type',\n label: t('messages.filters.type', 'Type'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],\n },\n {\n id: 'hasObjects',\n label: t('messages.filters.hasObjects', 'Has related records'),\n tooltip: t('messages.filters.hasObjectsTooltip', 'Shows messages that have Open Mercato records attached \u2014 such as orders, quotes, or customers.'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasAttachments',\n label: t('messages.filters.hasAttachments', 'Has attachments'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasActions',\n label: t('messages.filters.hasActions', 'Has action requests'),\n tooltip: t('messages.filters.hasActionsTooltip', 'Shows messages where one or more attached records require a response (approval, rejection, or review).'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'senderId',\n label: t('messages.filters.sender', 'Sender'),\n type: 'combobox',\n placeholder: t('messages.filters.senderEmpty', 'Type to search users'),\n options: senderOptions,\n loadOptions: loadSenderOptions,\n formatValue: senderLabel,\n formatDescription: (val: string) =>\n senderOptions.find((opt) => opt.value === val)?.description ?? null,\n },\n {\n id: 'since',\n label: t('messages.filters.since', 'Sent after'),\n type: 'text',\n placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DD'),\n },\n ]\n}\n\nconst DATE_ONLY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/\n\nexport function normalizeMessagesSinceValue(input: string): string | null {\n const trimmed = input.trim()\n if (!trimmed) return null\n if (DATE_ONLY_PATTERN.test(trimmed)) {\n const parsed = new Date(`${trimmed}T00:00:00.000Z`)\n if (Number.isNaN(parsed.getTime())) return null\n if (parsed.toISOString().slice(0, 10) !== trimmed) return null\n return parsed.toISOString()\n }\n const parsed = new Date(trimmed)\n if (Number.isNaN(parsed.getTime())) return null\n return parsed.toISOString()\n}\n\nexport type BuildMessagesListParamsOptions = {\n folder: string\n page: number\n pageSize: number\n search: string\n filterValues: FilterValues\n}\n\nexport function buildMessagesListParams({\n folder,\n page,\n pageSize,\n search,\n filterValues,\n}: BuildMessagesListParamsOptions): URLSearchParams {\n const params = new URLSearchParams()\n params.set('folder', folder)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n\n if (search.trim()) {\n params.set('search', search.trim())\n }\n\n const stringValue = (id: string): string => {\n const raw = filterValues[id]\n return typeof raw === 'string' ? raw.trim() : ''\n }\n\n const status = stringValue('status')\n const type = stringValue('type')\n const hasObjects = stringValue('hasObjects')\n const hasAttachments = stringValue('hasAttachments')\n const hasActions = stringValue('hasActions')\n const senderId = stringValue('senderId')\n const sinceRaw = stringValue('since')\n\n if (status) params.set('status', status)\n if (type) params.set('type', type)\n if (hasObjects) params.set('hasObjects', hasObjects)\n if (hasAttachments) params.set('hasAttachments', hasAttachments)\n if (hasActions) params.set('hasActions', hasActions)\n if (senderId) params.set('senderId', senderId)\n if (sinceRaw) {\n const normalized = normalizeMessagesSinceValue(sinceRaw)\n if (normalized) params.set('since', normalized)\n }\n\n return params\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\n\ntype Translator = (key: string, fallback?: string) => string\n\nexport type SenderOption = {\n value: string\n label: string\n description?: string | null\n}\n\ntype SenderOptionSource = {\n senderUserId?: string | null\n senderName?: string | null\n senderEmail?: string | null\n}\n\n/**\n * Sender-filter options derived from the rows currently in the list.\n *\n * The filter narrows by `senderUserId`, so a row only yields an honest option\n * when it also carries the platform identity behind that id. Ingested external\n * mail does not: every such message in a tenant is composed under one\n * system-user id, so labelling that id with one external sender's name would\n * collapse every external sender into a single option that quietly returns all\n * of their mail. Those rows are skipped, and the directory-loaded options \u2014\n * which are real platform users \u2014 stand alone.\n */\nexport function buildSenderOptionsFromMessages(items: SenderOptionSource[]): SenderOption[] {\n return items.flatMap((item): SenderOption[] => {\n if (typeof item.senderUserId !== 'string' || item.senderUserId.trim().length === 0) return []\n const name = typeof item.senderName === 'string' && item.senderName.trim().length > 0\n ? item.senderName.trim()\n : null\n const email = typeof item.senderEmail === 'string' && item.senderEmail.trim().length > 0\n ? item.senderEmail.trim()\n : null\n const label = name ?? email\n if (!label) return []\n return [{\n value: item.senderUserId,\n label,\n description: email && email !== label ? email : null,\n }]\n })\n}\n\nexport type MessagesInboxFilterContext = {\n t: Translator\n typeOptions: { value: string; label: string }[]\n senderOptions: SenderOption[]\n loadSenderOptions: (query?: string) => Promise<SenderOption[]>\n}\n\nexport function buildMessagesInboxFilters({\n t,\n typeOptions,\n senderOptions,\n loadSenderOptions,\n}: MessagesInboxFilterContext): FilterDef[] {\n const senderLabel = (val: string): string => {\n const match = senderOptions.find((opt) => opt.value === val)\n return match?.label ?? val\n }\n\n return [\n {\n id: 'status',\n label: t('messages.filters.status', 'Status'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'unread', label: t('messages.status.unread', 'Unread') },\n { value: 'read', label: t('messages.status.read', 'Read') },\n { value: 'archived', label: t('messages.status.archived', 'Archived') },\n ],\n },\n {\n id: 'type',\n label: t('messages.filters.type', 'Type'),\n type: 'select',\n options: [{ value: '', label: t('messages.filters.all', 'All') }, ...typeOptions],\n },\n {\n id: 'hasObjects',\n label: t('messages.filters.hasObjects', 'Has related records'),\n tooltip: t('messages.filters.hasObjectsTooltip', 'Shows messages that have Open Mercato records attached \u2014 such as orders, quotes, or customers.'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasAttachments',\n label: t('messages.filters.hasAttachments', 'Has attachments'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'hasActions',\n label: t('messages.filters.hasActions', 'Has action requests'),\n tooltip: t('messages.filters.hasActionsTooltip', 'Shows messages where one or more attached records require a response (approval, rejection, or review).'),\n type: 'select',\n options: [\n { value: '', label: t('messages.filters.all', 'All') },\n { value: 'true', label: t('common.yes', 'Yes') },\n { value: 'false', label: t('common.no', 'No') },\n ],\n },\n {\n id: 'senderId',\n label: t('messages.filters.sender', 'Sender'),\n type: 'combobox',\n placeholder: t('messages.filters.senderEmpty', 'Type to search users'),\n options: senderOptions,\n loadOptions: loadSenderOptions,\n formatValue: senderLabel,\n formatDescription: (val: string) =>\n senderOptions.find((opt) => opt.value === val)?.description ?? null,\n },\n {\n id: 'since',\n label: t('messages.filters.since', 'Sent after'),\n type: 'text',\n placeholder: t('messages.filters.sincePlaceholder', 'YYYY-MM-DD'),\n },\n ]\n}\n\nconst DATE_ONLY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/\n\nexport function normalizeMessagesSinceValue(input: string): string | null {\n const trimmed = input.trim()\n if (!trimmed) return null\n if (DATE_ONLY_PATTERN.test(trimmed)) {\n const parsed = new Date(`${trimmed}T00:00:00.000Z`)\n if (Number.isNaN(parsed.getTime())) return null\n if (parsed.toISOString().slice(0, 10) !== trimmed) return null\n return parsed.toISOString()\n }\n const parsed = new Date(trimmed)\n if (Number.isNaN(parsed.getTime())) return null\n return parsed.toISOString()\n}\n\nexport type BuildMessagesListParamsOptions = {\n folder: string\n page: number\n pageSize: number\n search: string\n filterValues: FilterValues\n}\n\nexport function buildMessagesListParams({\n folder,\n page,\n pageSize,\n search,\n filterValues,\n}: BuildMessagesListParamsOptions): URLSearchParams {\n const params = new URLSearchParams()\n params.set('folder', folder)\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n\n if (search.trim()) {\n params.set('search', search.trim())\n }\n\n const stringValue = (id: string): string => {\n const raw = filterValues[id]\n return typeof raw === 'string' ? raw.trim() : ''\n }\n\n const status = stringValue('status')\n const type = stringValue('type')\n const hasObjects = stringValue('hasObjects')\n const hasAttachments = stringValue('hasAttachments')\n const hasActions = stringValue('hasActions')\n const senderId = stringValue('senderId')\n const sinceRaw = stringValue('since')\n\n if (status) params.set('status', status)\n if (type) params.set('type', type)\n if (hasObjects) params.set('hasObjects', hasObjects)\n if (hasAttachments) params.set('hasAttachments', hasAttachments)\n if (hasActions) params.set('hasActions', hasActions)\n if (senderId) params.set('senderId', senderId)\n if (sinceRaw) {\n const normalized = normalizeMessagesSinceValue(sinceRaw)\n if (normalized) params.set('since', normalized)\n }\n\n return params\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["parsed"]
|
|
7
7
|
}
|
package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { getMessageParticipantLabel } from "../../messageListLabels.js";
|
|
3
4
|
function useMessageDetailsConversation({
|
|
4
5
|
detail,
|
|
5
6
|
t
|
|
@@ -12,6 +13,9 @@ function useMessageDetailsConversation({
|
|
|
12
13
|
senderUserId: detail.senderUserId,
|
|
13
14
|
senderName: detail.senderName,
|
|
14
15
|
senderEmail: detail.senderEmail,
|
|
16
|
+
externalName: detail.externalName,
|
|
17
|
+
externalEmail: detail.externalEmail,
|
|
18
|
+
sourceEntityType: detail.sourceEntityType,
|
|
15
19
|
body: detail.body,
|
|
16
20
|
bodyFormat: detail.bodyFormat,
|
|
17
21
|
sentAt: detail.sentAt,
|
|
@@ -23,6 +27,9 @@ function useMessageDetailsConversation({
|
|
|
23
27
|
senderUserId: item.senderUserId,
|
|
24
28
|
senderName: item.senderName,
|
|
25
29
|
senderEmail: item.senderEmail,
|
|
30
|
+
externalName: item.externalName,
|
|
31
|
+
externalEmail: item.externalEmail,
|
|
32
|
+
sourceEntityType: item.sourceEntityType,
|
|
26
33
|
body: item.body,
|
|
27
34
|
bodyFormat: item.bodyFormat,
|
|
28
35
|
sentAt: item.sentAt,
|
|
@@ -34,6 +41,9 @@ function useMessageDetailsConversation({
|
|
|
34
41
|
senderUserId: detail.senderUserId,
|
|
35
42
|
senderName: detail.senderName,
|
|
36
43
|
senderEmail: detail.senderEmail,
|
|
44
|
+
externalName: detail.externalName,
|
|
45
|
+
externalEmail: detail.externalEmail,
|
|
46
|
+
sourceEntityType: detail.sourceEntityType,
|
|
37
47
|
body: detail.body,
|
|
38
48
|
bodyFormat: detail.bodyFormat,
|
|
39
49
|
sentAt: detail.sentAt,
|
|
@@ -80,7 +90,7 @@ function useMessageDetailsConversation({
|
|
|
80
90
|
bodyFormat: item.bodyFormat ?? "text",
|
|
81
91
|
priority: "normal",
|
|
82
92
|
sentAt: item.sentAt ? new Date(item.sentAt) : null,
|
|
83
|
-
senderName: item
|
|
93
|
+
senderName: getMessageParticipantLabel(item),
|
|
84
94
|
hasObjects: (item.objects ?? []).length > 0,
|
|
85
95
|
objectCount: (item.objects ?? []).length,
|
|
86
96
|
hasAttachments: false,
|
package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport type { MessageListItemProps } from '@open-mercato/shared/modules/messages/types'\nimport type { MessageDetail } from '../types'\n\nexport type ConversationItem = {\n id: string\n senderUserId: string\n senderName?: string | null\n senderEmail?: string | null\n body: string\n bodyFormat?: 'text' | 'markdown'\n sentAt?: string | null\n objects?: Array<{ id: string }>\n}\n\ntype UseMessageDetailsConversationInput = {\n detail: MessageDetail | null\n t: TranslateFn\n}\n\nexport function useMessageDetailsConversation({\n detail,\n t,\n}: UseMessageDetailsConversationInput) {\n const conversationItems = React.useMemo<ConversationItem[]>(() => {\n const thread = detail?.thread ?? []\n if (thread.length === 0 && detail) {\n return [{\n id: detail.id,\n senderUserId: detail.senderUserId,\n senderName: detail.senderName,\n senderEmail: detail.senderEmail,\n body: detail.body,\n bodyFormat: detail.bodyFormat,\n sentAt: detail.sentAt,\n objects: detail.objects.map((item) => ({ id: item.id })),\n }]\n }\n\n const items = thread\n .map((item) => ({\n id: item.id,\n senderUserId: item.senderUserId,\n senderName: item.senderName,\n senderEmail: item.senderEmail,\n body: item.body,\n bodyFormat: item.bodyFormat,\n sentAt: item.sentAt,\n objects: item.objects?.map((entry) => ({ id: entry.id })) ?? [],\n }))\n\n if (detail && !items.some((item) => item.id === detail.id)) {\n items.push({\n id: detail.id,\n senderUserId: detail.senderUserId,\n senderName: detail.senderName,\n senderEmail: detail.senderEmail,\n body: detail.body,\n bodyFormat: detail.bodyFormat,\n sentAt: detail.sentAt,\n objects: detail.objects.map((entry) => ({ id: entry.id })),\n })\n }\n\n items.sort((a, b) => {\n const aTime = a.sentAt ? new Date(a.sentAt).getTime() : 0\n const bTime = b.sentAt ? new Date(b.sentAt).getTime() : 0\n return aTime - bTime\n })\n\n return items\n }, [detail])\n\n const forcedExpandedItemId = conversationItems.length > 0\n ? conversationItems[conversationItems.length - 1].id\n : null\n\n const [expandedById, setExpandedById] = React.useState<Record<string, boolean>>({})\n const conversationIdsKey = React.useMemo(\n () => conversationItems.map((item) => item.id).join('|'),\n [conversationItems],\n )\n\n React.useEffect(() => {\n const nextState: Record<string, boolean> = {}\n for (const item of conversationItems) {\n nextState[item.id] = forcedExpandedItemId === item.id\n }\n setExpandedById(nextState)\n }, [conversationIdsKey, conversationItems, forcedExpandedItemId])\n\n const toggleConversationItem = React.useCallback((itemId: string) => {\n if (itemId === forcedExpandedItemId) return\n setExpandedById((previous) => ({\n ...previous,\n [itemId]: !previous[itemId],\n }))\n }, [forcedExpandedItemId])\n\n const isConversationItemExpanded = React.useCallback((itemId: string) => {\n if (itemId === forcedExpandedItemId) return true\n return Boolean(expandedById[itemId])\n }, [expandedById, forcedExpandedItemId])\n\n const buildConversationListItemMessage = React.useCallback((item: ConversationItem): MessageListItemProps['message'] => ({\n id: item.id,\n type: detail?.type ?? 'default',\n typeLabel: detail ? t(detail.typeDefinition.labelKey, detail.type) : undefined,\n subject: detail?.subject ?? '',\n body: item.body,\n bodyFormat: item.bodyFormat ?? 'text',\n priority: 'normal',\n sentAt: item.sentAt ? new Date(item.sentAt) : null,\n senderName: item
|
|
5
|
-
"mappings": ";AAEA,YAAY,WAAW;
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport type { MessageListItemProps } from '@open-mercato/shared/modules/messages/types'\nimport type { MessageDetail } from '../types'\nimport { getMessageParticipantLabel } from '../../messageListLabels'\n\nexport type ConversationItem = {\n id: string\n senderUserId: string\n senderName?: string | null\n senderEmail?: string | null\n externalName?: string | null\n externalEmail?: string | null\n sourceEntityType?: string | null\n body: string\n bodyFormat?: 'text' | 'markdown'\n sentAt?: string | null\n objects?: Array<{ id: string }>\n}\n\ntype UseMessageDetailsConversationInput = {\n detail: MessageDetail | null\n t: TranslateFn\n}\n\nexport function useMessageDetailsConversation({\n detail,\n t,\n}: UseMessageDetailsConversationInput) {\n const conversationItems = React.useMemo<ConversationItem[]>(() => {\n const thread = detail?.thread ?? []\n if (thread.length === 0 && detail) {\n return [{\n id: detail.id,\n senderUserId: detail.senderUserId,\n senderName: detail.senderName,\n senderEmail: detail.senderEmail,\n externalName: detail.externalName,\n externalEmail: detail.externalEmail,\n sourceEntityType: detail.sourceEntityType,\n body: detail.body,\n bodyFormat: detail.bodyFormat,\n sentAt: detail.sentAt,\n objects: detail.objects.map((item) => ({ id: item.id })),\n }]\n }\n\n const items = thread\n .map((item) => ({\n id: item.id,\n senderUserId: item.senderUserId,\n senderName: item.senderName,\n senderEmail: item.senderEmail,\n externalName: item.externalName,\n externalEmail: item.externalEmail,\n sourceEntityType: item.sourceEntityType,\n body: item.body,\n bodyFormat: item.bodyFormat,\n sentAt: item.sentAt,\n objects: item.objects?.map((entry) => ({ id: entry.id })) ?? [],\n }))\n\n if (detail && !items.some((item) => item.id === detail.id)) {\n items.push({\n id: detail.id,\n senderUserId: detail.senderUserId,\n senderName: detail.senderName,\n senderEmail: detail.senderEmail,\n externalName: detail.externalName,\n externalEmail: detail.externalEmail,\n sourceEntityType: detail.sourceEntityType,\n body: detail.body,\n bodyFormat: detail.bodyFormat,\n sentAt: detail.sentAt,\n objects: detail.objects.map((entry) => ({ id: entry.id })),\n })\n }\n\n items.sort((a, b) => {\n const aTime = a.sentAt ? new Date(a.sentAt).getTime() : 0\n const bTime = b.sentAt ? new Date(b.sentAt).getTime() : 0\n return aTime - bTime\n })\n\n return items\n }, [detail])\n\n const forcedExpandedItemId = conversationItems.length > 0\n ? conversationItems[conversationItems.length - 1].id\n : null\n\n const [expandedById, setExpandedById] = React.useState<Record<string, boolean>>({})\n const conversationIdsKey = React.useMemo(\n () => conversationItems.map((item) => item.id).join('|'),\n [conversationItems],\n )\n\n React.useEffect(() => {\n const nextState: Record<string, boolean> = {}\n for (const item of conversationItems) {\n nextState[item.id] = forcedExpandedItemId === item.id\n }\n setExpandedById(nextState)\n }, [conversationIdsKey, conversationItems, forcedExpandedItemId])\n\n const toggleConversationItem = React.useCallback((itemId: string) => {\n if (itemId === forcedExpandedItemId) return\n setExpandedById((previous) => ({\n ...previous,\n [itemId]: !previous[itemId],\n }))\n }, [forcedExpandedItemId])\n\n const isConversationItemExpanded = React.useCallback((itemId: string) => {\n if (itemId === forcedExpandedItemId) return true\n return Boolean(expandedById[itemId])\n }, [expandedById, forcedExpandedItemId])\n\n const buildConversationListItemMessage = React.useCallback((item: ConversationItem): MessageListItemProps['message'] => ({\n id: item.id,\n type: detail?.type ?? 'default',\n typeLabel: detail ? t(detail.typeDefinition.labelKey, detail.type) : undefined,\n subject: detail?.subject ?? '',\n body: item.body,\n bodyFormat: item.bodyFormat ?? 'text',\n priority: 'normal',\n sentAt: item.sentAt ? new Date(item.sentAt) : null,\n senderName: getMessageParticipantLabel(item),\n hasObjects: (item.objects ?? []).length > 0,\n objectCount: (item.objects ?? []).length,\n hasAttachments: false,\n attachmentCount: 0,\n hasActions: false,\n actionTaken: null,\n unread: false,\n }), [detail, t])\n\n return {\n conversationItems,\n forcedExpandedItemId,\n toggleConversationItem,\n isConversationItemExpanded,\n buildConversationListItemMessage,\n }\n}\n"],
|
|
5
|
+
"mappings": ";AAEA,YAAY,WAAW;AAIvB,SAAS,kCAAkC;AAqBpC,SAAS,8BAA8B;AAAA,EAC5C;AAAA,EACA;AACF,GAAuC;AACrC,QAAM,oBAAoB,MAAM,QAA4B,MAAM;AAChE,UAAM,SAAS,QAAQ,UAAU,CAAC;AAClC,QAAI,OAAO,WAAW,KAAK,QAAQ;AACjC,aAAO,CAAC;AAAA,QACN,IAAI,OAAO;AAAA,QACX,cAAc,OAAO;AAAA,QACrB,YAAY,OAAO;AAAA,QACnB,aAAa,OAAO;AAAA,QACpB,cAAc,OAAO;AAAA,QACrB,eAAe,OAAO;AAAA,QACtB,kBAAkB,OAAO;AAAA,QACzB,MAAM,OAAO;AAAA,QACb,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,SAAS,OAAO,QAAQ,IAAI,CAAC,UAAU,EAAE,IAAI,KAAK,GAAG,EAAE;AAAA,MACzD,CAAC;AAAA,IACH;AAEA,UAAM,QAAQ,OACX,IAAI,CAAC,UAAU;AAAA,MACd,IAAI,KAAK;AAAA,MACT,cAAc,KAAK;AAAA,MACnB,YAAY,KAAK;AAAA,MACjB,aAAa,KAAK;AAAA,MAClB,cAAc,KAAK;AAAA,MACnB,eAAe,KAAK;AAAA,MACpB,kBAAkB,KAAK;AAAA,MACvB,MAAM,KAAK;AAAA,MACX,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK,SAAS,IAAI,CAAC,WAAW,EAAE,IAAI,MAAM,GAAG,EAAE,KAAK,CAAC;AAAA,IAChE,EAAE;AAEJ,QAAI,UAAU,CAAC,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,OAAO,EAAE,GAAG;AAC1D,YAAM,KAAK;AAAA,QACT,IAAI,OAAO;AAAA,QACX,cAAc,OAAO;AAAA,QACrB,YAAY,OAAO;AAAA,QACnB,aAAa,OAAO;AAAA,QACpB,cAAc,OAAO;AAAA,QACrB,eAAe,OAAO;AAAA,QACtB,kBAAkB,OAAO;AAAA,QACzB,MAAM,OAAO;AAAA,QACb,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,SAAS,OAAO,QAAQ,IAAI,CAAC,WAAW,EAAE,IAAI,MAAM,GAAG,EAAE;AAAA,MAC3D,CAAC;AAAA,IACH;AAEA,UAAM,KAAK,CAAC,GAAG,MAAM;AACnB,YAAM,QAAQ,EAAE,SAAS,IAAI,KAAK,EAAE,MAAM,EAAE,QAAQ,IAAI;AACxD,YAAM,QAAQ,EAAE,SAAS,IAAI,KAAK,EAAE,MAAM,EAAE,QAAQ,IAAI;AACxD,aAAO,QAAQ;AAAA,IACjB,CAAC;AAED,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,uBAAuB,kBAAkB,SAAS,IACpD,kBAAkB,kBAAkB,SAAS,CAAC,EAAE,KAChD;AAEJ,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAkC,CAAC,CAAC;AAClF,QAAM,qBAAqB,MAAM;AAAA,IAC/B,MAAM,kBAAkB,IAAI,CAAC,SAAS,KAAK,EAAE,EAAE,KAAK,GAAG;AAAA,IACvD,CAAC,iBAAiB;AAAA,EACpB;AAEA,QAAM,UAAU,MAAM;AACpB,UAAM,YAAqC,CAAC;AAC5C,eAAW,QAAQ,mBAAmB;AACpC,gBAAU,KAAK,EAAE,IAAI,yBAAyB,KAAK;AAAA,IACrD;AACA,oBAAgB,SAAS;AAAA,EAC3B,GAAG,CAAC,oBAAoB,mBAAmB,oBAAoB,CAAC;AAEhE,QAAM,yBAAyB,MAAM,YAAY,CAAC,WAAmB;AACnE,QAAI,WAAW,qBAAsB;AACrC,oBAAgB,CAAC,cAAc;AAAA,MAC7B,GAAG;AAAA,MACH,CAAC,MAAM,GAAG,CAAC,SAAS,MAAM;AAAA,IAC5B,EAAE;AAAA,EACJ,GAAG,CAAC,oBAAoB,CAAC;AAEzB,QAAM,6BAA6B,MAAM,YAAY,CAAC,WAAmB;AACvE,QAAI,WAAW,qBAAsB,QAAO;AAC5C,WAAO,QAAQ,aAAa,MAAM,CAAC;AAAA,EACrC,GAAG,CAAC,cAAc,oBAAoB,CAAC;AAEvC,QAAM,mCAAmC,MAAM,YAAY,CAAC,UAA6D;AAAA,IACvH,IAAI,KAAK;AAAA,IACT,MAAM,QAAQ,QAAQ;AAAA,IACtB,WAAW,SAAS,EAAE,OAAO,eAAe,UAAU,OAAO,IAAI,IAAI;AAAA,IACrE,SAAS,QAAQ,WAAW;AAAA,IAC5B,MAAM,KAAK;AAAA,IACX,YAAY,KAAK,cAAc;AAAA,IAC/B,UAAU;AAAA,IACV,QAAQ,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,IAAI;AAAA,IAC9C,YAAY,2BAA2B,IAAI;AAAA,IAC3C,aAAa,KAAK,WAAW,CAAC,GAAG,SAAS;AAAA,IAC1C,cAAc,KAAK,WAAW,CAAC,GAAG;AAAA,IAClC,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,QAAQ;AAAA,EACV,IAAI,CAAC,QAAQ,CAAC,CAAC;AAEf,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -17,10 +17,12 @@ import {
|
|
|
17
17
|
Trash2
|
|
18
18
|
} from "lucide-react";
|
|
19
19
|
import { formatDateTime } from "../utils.js";
|
|
20
|
+
import { getMessageParticipantLabel } from "../../messageListLabels.js";
|
|
20
21
|
function MessageHeader(props) {
|
|
21
22
|
const t = useT();
|
|
22
23
|
const showSubject = props.showSubject !== false;
|
|
23
24
|
const canReply = !props.detail.isDraft && props.detail.typeDefinition.allowReply;
|
|
25
|
+
const senderLabel = getMessageParticipantLabel(props.detail);
|
|
24
26
|
const actionItems = React.useMemo(() => {
|
|
25
27
|
const items = [];
|
|
26
28
|
if (props.detail.isDraft && props.detail.canEditDraft) {
|
|
@@ -84,7 +86,7 @@ function MessageHeader(props) {
|
|
|
84
86
|
{
|
|
85
87
|
mode: "detail",
|
|
86
88
|
title: showSubject ? props.detail.subject : void 0,
|
|
87
|
-
subtitle: `${t("messages.detail.from", "From")}: ${
|
|
89
|
+
subtitle: `${t("messages.detail.from", "From")}: ${senderLabel}`,
|
|
88
90
|
statusBadge: /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: formatDateTime(props.detail.sentAt) }),
|
|
89
91
|
utilityActions: canReply ? /* @__PURE__ */ jsx(
|
|
90
92
|
IconButton,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/messages/components/message-detail/panels/MessageHeader.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { FormHeader, type ActionItem } from '@open-mercato/ui/backend/forms'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport {\n Archive,\n ArchiveRestore,\n ChevronDown,\n ChevronUp,\n FilePenLine,\n Forward,\n Mail,\n MailOpen,\n Reply,\n Trash2,\n} from 'lucide-react'\nimport type { MessageDetail } from '../types'\nimport { formatDateTime } from '../utils'\n\ntype MessageHeaderProps = {\n detail: MessageDetail\n updatingState: boolean\n isArchived: boolean\n showSubject?: boolean\n onReply: () => void\n onForward: () => void\n onEdit: () => void\n onToggleRead: () => void\n onToggleArchive: () => void\n onDelete: () => void\n collapseToggle?: {\n expanded: boolean\n onToggle: () => void\n }\n}\n\nexport function MessageHeader(props: MessageHeaderProps) {\n const t = useT()\n const showSubject = props.showSubject !== false\n const canReply = !props.detail.isDraft && props.detail.typeDefinition.allowReply\n const actionItems = React.useMemo(() => {\n const items: ActionItem[] = []\n\n if (props.detail.isDraft && props.detail.canEditDraft) {\n items.push({\n id: 'edit',\n label: t('messages.actions.edit', 'Edit'),\n icon: FilePenLine,\n onSelect: props.onEdit,\n })\n }\n\n if (!props.detail.isDraft && props.detail.typeDefinition.allowForward) {\n items.push({\n id: 'forward',\n label: t('messages.forward', 'Forward'),\n icon: Forward,\n onSelect: props.onForward,\n })\n }\n\n if (!props.detail.isDraft) {\n items.push({\n id: 'toggle-read',\n label: props.detail.isRead\n ? t('messages.actions.markUnread', 'Mark unread')\n : t('messages.actions.markRead', 'Mark read'),\n icon: props.detail.isRead ? Mail : MailOpen,\n onSelect: props.onToggleRead,\n disabled: props.updatingState,\n })\n }\n\n if (!props.detail.isDraft && props.detail.canArchive) {\n items.push({\n id: 'toggle-archive',\n label: props.isArchived\n ? t('messages.actions.unarchive', 'Unarchive')\n : t('messages.actions.archive', 'Archive'),\n icon: props.isArchived ? ArchiveRestore : Archive,\n onSelect: props.onToggleArchive,\n disabled: props.updatingState,\n })\n }\n\n items.push({\n id: 'delete',\n label: t('messages.actions.delete', 'Delete'),\n icon: Trash2,\n onSelect: props.onDelete,\n disabled: props.updatingState,\n })\n\n return items\n }, [props, t])\n\n return (\n <div className=\"flex items-start gap-2\">\n {props.collapseToggle ? (\n <IconButton\n type=\"button\"\n onClick={props.collapseToggle.onToggle}\n variant=\"ghost\"\n size=\"sm\"\n className=\"mt-1 text-muted-foreground hover:text-foreground\"\n aria-label={props.collapseToggle.expanded\n ? t('messages.detail.hideDetails', 'Hide details')\n : t('messages.detail.showDetails', 'Show details')}\n >\n {props.collapseToggle.expanded ? (\n <ChevronUp className=\"h-4 w-4\" aria-hidden />\n ) : (\n <ChevronDown className=\"h-4 w-4\" aria-hidden />\n )}\n </IconButton>\n ) : null}\n\n <div className=\"min-w-0 flex-1\">\n <FormHeader\n mode=\"detail\"\n title={showSubject ? props.detail.subject : undefined}\n subtitle={`${t('messages.detail.from', 'From')}: ${
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { FormHeader, type ActionItem } from '@open-mercato/ui/backend/forms'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport {\n Archive,\n ArchiveRestore,\n ChevronDown,\n ChevronUp,\n FilePenLine,\n Forward,\n Mail,\n MailOpen,\n Reply,\n Trash2,\n} from 'lucide-react'\nimport type { MessageDetail } from '../types'\nimport { formatDateTime } from '../utils'\nimport { getMessageParticipantLabel } from '../../messageListLabels'\n\ntype MessageHeaderProps = {\n detail: MessageDetail\n updatingState: boolean\n isArchived: boolean\n showSubject?: boolean\n onReply: () => void\n onForward: () => void\n onEdit: () => void\n onToggleRead: () => void\n onToggleArchive: () => void\n onDelete: () => void\n collapseToggle?: {\n expanded: boolean\n onToggle: () => void\n }\n}\n\nexport function MessageHeader(props: MessageHeaderProps) {\n const t = useT()\n const showSubject = props.showSubject !== false\n const canReply = !props.detail.isDraft && props.detail.typeDefinition.allowReply\n const senderLabel = getMessageParticipantLabel(props.detail)\n const actionItems = React.useMemo(() => {\n const items: ActionItem[] = []\n\n if (props.detail.isDraft && props.detail.canEditDraft) {\n items.push({\n id: 'edit',\n label: t('messages.actions.edit', 'Edit'),\n icon: FilePenLine,\n onSelect: props.onEdit,\n })\n }\n\n if (!props.detail.isDraft && props.detail.typeDefinition.allowForward) {\n items.push({\n id: 'forward',\n label: t('messages.forward', 'Forward'),\n icon: Forward,\n onSelect: props.onForward,\n })\n }\n\n if (!props.detail.isDraft) {\n items.push({\n id: 'toggle-read',\n label: props.detail.isRead\n ? t('messages.actions.markUnread', 'Mark unread')\n : t('messages.actions.markRead', 'Mark read'),\n icon: props.detail.isRead ? Mail : MailOpen,\n onSelect: props.onToggleRead,\n disabled: props.updatingState,\n })\n }\n\n if (!props.detail.isDraft && props.detail.canArchive) {\n items.push({\n id: 'toggle-archive',\n label: props.isArchived\n ? t('messages.actions.unarchive', 'Unarchive')\n : t('messages.actions.archive', 'Archive'),\n icon: props.isArchived ? ArchiveRestore : Archive,\n onSelect: props.onToggleArchive,\n disabled: props.updatingState,\n })\n }\n\n items.push({\n id: 'delete',\n label: t('messages.actions.delete', 'Delete'),\n icon: Trash2,\n onSelect: props.onDelete,\n disabled: props.updatingState,\n })\n\n return items\n }, [props, t])\n\n return (\n <div className=\"flex items-start gap-2\">\n {props.collapseToggle ? (\n <IconButton\n type=\"button\"\n onClick={props.collapseToggle.onToggle}\n variant=\"ghost\"\n size=\"sm\"\n className=\"mt-1 text-muted-foreground hover:text-foreground\"\n aria-label={props.collapseToggle.expanded\n ? t('messages.detail.hideDetails', 'Hide details')\n : t('messages.detail.showDetails', 'Show details')}\n >\n {props.collapseToggle.expanded ? (\n <ChevronUp className=\"h-4 w-4\" aria-hidden />\n ) : (\n <ChevronDown className=\"h-4 w-4\" aria-hidden />\n )}\n </IconButton>\n ) : null}\n\n <div className=\"min-w-0 flex-1\">\n <FormHeader\n mode=\"detail\"\n title={showSubject ? props.detail.subject : undefined}\n subtitle={`${t('messages.detail.from', 'From')}: ${senderLabel}`}\n statusBadge={<p className=\"text-xs text-muted-foreground\">{formatDateTime(props.detail.sentAt)}</p>}\n utilityActions={canReply ? (\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"default\"\n aria-label={t('messages.reply', 'Reply')}\n onClick={props.onReply}\n >\n <Reply className=\"h-4 w-4\" aria-hidden />\n </IconButton>\n ) : undefined}\n menuTriggerMode=\"icon\"\n menuAriaLabel={t('ui.actions.actions', 'Actions')}\n menuActions={actionItems}\n />\n </div>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,7 @@ import { MarkdownContent } from "@open-mercato/ui/backend/markdown/MarkdownConte
|
|
|
5
5
|
import { getMessageUiComponentRegistry } from "../../utils/typeUiRegistry.js";
|
|
6
6
|
import { getMessageObjectType } from "../../../lib/message-objects-registry.js";
|
|
7
7
|
import { formatDateTime } from "../utils.js";
|
|
8
|
+
import { getMessageParticipantLabel } from "../../messageListLabels.js";
|
|
8
9
|
function MessageDetailThreadSection({ detail }) {
|
|
9
10
|
const t = useT();
|
|
10
11
|
const messageUiRegistry = getMessageUiComponentRegistry();
|
|
@@ -13,7 +14,7 @@ function MessageDetailThreadSection({ detail }) {
|
|
|
13
14
|
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: t("messages.detail.thread", "Thread") }),
|
|
14
15
|
/* @__PURE__ */ jsx("div", { className: "space-y-3", children: (detail.thread ?? []).map((threadItem) => /* @__PURE__ */ jsxs("article", { className: "rounded border p-3", children: [
|
|
15
16
|
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
16
|
-
threadItem
|
|
17
|
+
getMessageParticipantLabel(threadItem),
|
|
17
18
|
" \u2022 ",
|
|
18
19
|
formatDateTime(threadItem.sentAt)
|
|
19
20
|
] }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/messages/components/message-detail/panels/thread-panel.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { MarkdownContent } from '@open-mercato/ui/backend/markdown/MarkdownContent'\nimport { getMessageUiComponentRegistry } from '../../utils/typeUiRegistry'\nimport { getMessageObjectType } from '../../../lib/message-objects-registry'\nimport type { MessageDetail, MessageDetailObject } from '../types'\nimport { formatDateTime } from '../utils'\n\nexport function MessageDetailThreadSection({ detail }: { detail: MessageDetail }) {\n const t = useT()\n const messageUiRegistry = getMessageUiComponentRegistry()\n\n if ((detail.thread ?? []).length === 0) return null\n\n return (\n <section className=\"space-y-3 border-l pl-4 py-2\">\n <h2 className=\"text-base font-semibold\">{t('messages.detail.thread', 'Thread')}</h2>\n <div className=\"space-y-3\">\n {(detail.thread ?? []).map((threadItem) => (\n <article key={threadItem.id} className=\"rounded border p-3\">\n <p className=\"text-xs text-muted-foreground\">\n {(threadItem
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { MarkdownContent } from '@open-mercato/ui/backend/markdown/MarkdownContent'\nimport { getMessageUiComponentRegistry } from '../../utils/typeUiRegistry'\nimport { getMessageObjectType } from '../../../lib/message-objects-registry'\nimport type { MessageDetail, MessageDetailObject } from '../types'\nimport { formatDateTime } from '../utils'\nimport { getMessageParticipantLabel } from '../../messageListLabels'\n\nexport function MessageDetailThreadSection({ detail }: { detail: MessageDetail }) {\n const t = useT()\n const messageUiRegistry = getMessageUiComponentRegistry()\n\n if ((detail.thread ?? []).length === 0) return null\n\n return (\n <section className=\"space-y-3 border-l pl-4 py-2\">\n <h2 className=\"text-base font-semibold\">{t('messages.detail.thread', 'Thread')}</h2>\n <div className=\"space-y-3\">\n {(detail.thread ?? []).map((threadItem) => (\n <article key={threadItem.id} className=\"rounded border p-3\">\n <p className=\"text-xs text-muted-foreground\">\n {getMessageParticipantLabel(threadItem)} \u2022 {formatDateTime(threadItem.sentAt)}\n </p>\n <div className=\"mt-2 max-h-[60vh] overflow-y-auto pr-1\">\n <MarkdownContent\n body={threadItem.body}\n format={threadItem.bodyFormat ?? 'text'}\n className=\"text-sm whitespace-pre-wrap [&>*]:mb-2 [&>*:last-child]:mb-0 [&_ul]:ml-4 [&_ul]:list-disc [&_ol]:ml-4 [&_ol]:list-decimal [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:text-xs\"\n />\n </div>\n\n {(threadItem.objects ?? []).length > 0 && (\n <div className=\"mt-3 space-y-2\">\n <p className=\"text-xs text-muted-foreground font-medium\">\n {t('messages.attachedObjects', 'Attached objects')}\n </p>\n {(threadItem.objects ?? []).map((obj: MessageDetailObject) => {\n const componentKey = `${obj.entityModule}:${obj.entityType}`\n const PreviewComponent = messageUiRegistry.objectPreviewComponents[componentKey]\n ?? messageUiRegistry.objectPreviewComponents['messages:default']\n const objectType = getMessageObjectType(obj.entityModule, obj.entityType)\n if (!PreviewComponent) return null\n\n return (\n <div key={obj.id} className=\"scale-95 origin-left\">\n <PreviewComponent\n entityId={obj.entityId}\n entityModule={obj.entityModule}\n entityType={obj.entityType}\n snapshot={obj.snapshot ?? undefined}\n previewData={obj.preview ?? undefined}\n actionRequired={obj.actionRequired}\n actionType={obj.actionType ?? undefined}\n actionLabel={obj.actionLabel ?? undefined}\n icon={objectType?.icon}\n />\n </div>\n )\n })}\n </div>\n )}\n </article>\n ))}\n </div>\n </section>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAkBM,cAIM,YAJN;AAhBN,SAAS,YAAY;AACrB,SAAS,uBAAuB;AAChC,SAAS,qCAAqC;AAC9C,SAAS,4BAA4B;AAErC,SAAS,sBAAsB;AAC/B,SAAS,kCAAkC;AAEpC,SAAS,2BAA2B,EAAE,OAAO,GAA8B;AAChF,QAAM,IAAI,KAAK;AACf,QAAM,oBAAoB,8BAA8B;AAExD,OAAK,OAAO,UAAU,CAAC,GAAG,WAAW,EAAG,QAAO;AAE/C,SACE,qBAAC,aAAQ,WAAU,gCACjB;AAAA,wBAAC,QAAG,WAAU,2BAA2B,YAAE,0BAA0B,QAAQ,GAAE;AAAA,IAC/E,oBAAC,SAAI,WAAU,aACX,kBAAO,UAAU,CAAC,GAAG,IAAI,CAAC,eAC1B,qBAAC,aAA4B,WAAU,sBACrC;AAAA,2BAAC,OAAE,WAAU,iCACV;AAAA,mCAA2B,UAAU;AAAA,QAAE;AAAA,QAAI,eAAe,WAAW,MAAM;AAAA,SAC9E;AAAA,MACA,oBAAC,SAAI,WAAU,0CACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,WAAW;AAAA,UACjB,QAAQ,WAAW,cAAc;AAAA,UACjC,WAAU;AAAA;AAAA,MACZ,GACF;AAAA,OAEE,WAAW,WAAW,CAAC,GAAG,SAAS,KACnC,qBAAC,SAAI,WAAU,kBACb;AAAA,4BAAC,OAAE,WAAU,6CACV,YAAE,4BAA4B,kBAAkB,GACnD;AAAA,SACE,WAAW,WAAW,CAAC,GAAG,IAAI,CAAC,QAA6B;AAC5D,gBAAM,eAAe,GAAG,IAAI,YAAY,IAAI,IAAI,UAAU;AAC1D,gBAAM,mBAAmB,kBAAkB,wBAAwB,YAAY,KAC1E,kBAAkB,wBAAwB,kBAAkB;AACjE,gBAAM,aAAa,qBAAqB,IAAI,cAAc,IAAI,UAAU;AACxE,cAAI,CAAC,iBAAkB,QAAO;AAE9B,iBACE,oBAAC,SAAiB,WAAU,wBAC1B;AAAA,YAAC;AAAA;AAAA,cACC,UAAU,IAAI;AAAA,cACd,cAAc,IAAI;AAAA,cAClB,YAAY,IAAI;AAAA,cAChB,UAAU,IAAI,YAAY;AAAA,cAC1B,aAAa,IAAI,WAAW;AAAA,cAC5B,gBAAgB,IAAI;AAAA,cACpB,YAAY,IAAI,cAAc;AAAA,cAC9B,aAAa,IAAI,eAAe;AAAA,cAChC,MAAM,YAAY;AAAA;AAAA,UACpB,KAXQ,IAAI,EAYd;AAAA,QAEJ,CAAC;AAAA,SACH;AAAA,SAxCU,WAAW,EA0CzB,CACD,GACH;AAAA,KACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
+
const INBOUND_EXTERNAL_SOURCE_ENTITY_TYPE = "communication_channels.external_conversation";
|
|
1
2
|
function normalizeLabel(value) {
|
|
2
3
|
const normalized = value?.trim();
|
|
3
4
|
return normalized ? normalized : null;
|
|
4
5
|
}
|
|
6
|
+
function isInboundExternalMessage(item) {
|
|
7
|
+
return item.sourceEntityType === INBOUND_EXTERNAL_SOURCE_ENTITY_TYPE;
|
|
8
|
+
}
|
|
9
|
+
function getMessageParticipantLabel(item) {
|
|
10
|
+
const platformIdentity = normalizeLabel(item.senderName) ?? normalizeLabel(item.senderEmail);
|
|
11
|
+
const externalIdentity = normalizeLabel(item.externalName) ?? normalizeLabel(item.externalEmail);
|
|
12
|
+
const preferred = isInboundExternalMessage(item) ? externalIdentity : platformIdentity;
|
|
13
|
+
const fallback = isInboundExternalMessage(item) ? platformIdentity : externalIdentity;
|
|
14
|
+
return preferred ?? fallback ?? item.senderUserId;
|
|
15
|
+
}
|
|
5
16
|
function getMessageListParticipantLabel(item, folder, t) {
|
|
6
17
|
if ((folder === "sent" || folder === "drafts") && Number(item.recipientCount ?? 0) <= 0) {
|
|
7
18
|
return t("messages.list.noRecipient", "(No recipient)");
|
|
8
19
|
}
|
|
9
|
-
return
|
|
20
|
+
return getMessageParticipantLabel(item);
|
|
10
21
|
}
|
|
11
22
|
export {
|
|
12
|
-
getMessageListParticipantLabel
|
|
23
|
+
getMessageListParticipantLabel,
|
|
24
|
+
getMessageParticipantLabel
|
|
13
25
|
};
|
|
14
26
|
//# sourceMappingURL=messageListLabels.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/components/messageListLabels.ts"],
|
|
4
|
-
"sourcesContent": ["import type { MessageFolder } from './useMessagesInboxBulkActions'\n\ntype Translate = (key: string, fallback: string, params?: Record<string, string | number>) => string\n\ntype MessageParticipantSource = {\n senderName?: string | null\n senderEmail?: string | null\n senderUserId: string\n recipientCount?: number | null\n}\n\nfunction normalizeLabel(value: string | null | undefined): string | null {\n const normalized = value?.trim()\n return normalized ? normalized : null\n}\n\nexport function getMessageListParticipantLabel(\n item: MessageParticipantSource,\n folder: MessageFolder,\n t: Translate,\n): string {\n if ((folder === 'sent' || folder === 'drafts') && Number(item.recipientCount ?? 0) <= 0) {\n return t('messages.list.noRecipient', '(No recipient)')\n }\n\n return
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { MessageFolder } from './useMessagesInboxBulkActions'\n\ntype Translate = (key: string, fallback: string, params?: Record<string, string | number>) => string\n\n/**\n * `Message.sourceEntityType` written by `communication_channels`'\n * `ingest-inbound-message` command, and by nothing else \u2014 the outbound bridge\n * treats it as the marker that a message arrived from outside the platform\n * rather than being sent from it. It is the only reliable inbound\n * discriminator: `type` is `channel.<provider>` on the outbound\n * `send-as-user` path too, where `externalName` carries the recipient side.\n */\nconst INBOUND_EXTERNAL_SOURCE_ENTITY_TYPE = 'communication_channels.external_conversation'\n\ntype MessageParticipantSource = {\n senderName?: string | null\n senderEmail?: string | null\n /**\n * The external counterparty on a message that crosses the platform boundary \u2014\n * the sender for an inbound email/chat ingested by `communication_channels`,\n * the recipient for an outbound reply sent through `inbox_ops`. Which side it\n * represents is decided by `sourceEntityType`, not by whether a platform\n * sender happens to be present.\n */\n externalName?: string | null\n externalEmail?: string | null\n sourceEntityType?: string | null\n senderUserId: string\n recipientCount?: number | null\n}\n\nfunction normalizeLabel(value: string | null | undefined): string | null {\n const normalized = value?.trim()\n return normalized ? normalized : null\n}\n\nfunction isInboundExternalMessage(item: MessageParticipantSource): boolean {\n return item.sourceEntityType === INBOUND_EXTERNAL_SOURCE_ENTITY_TYPE\n}\n\n/**\n * Human-readable label for a message's author.\n *\n * An ingested inbound message is authored by the external counterparty, even\n * though it is composed under a platform user id: that id is the\n * `communication_channels` system user, or \u2014 once an operator assigns the\n * conversation \u2014 the assigned agent, who resolves to a real name in the user\n * directory and would otherwise be printed as the author of the customer's own\n * email. So on the inbound side the external identity wins, and the platform\n * identity is only the fallback. Everywhere else the platform sender is the\n * author and the external identity describes the recipient, so the order is\n * reversed. The raw user id remains the last resort in both directions.\n *\n * The single source of truth for every place the messages module prints a\n * participant \u2014 the inbox list, the detail header and the conversation rows \u2014\n * so an ingested email renders as \"Jane Doe\" / \"jane@example.com\" rather than\n * the `communication_channels` system user id or the agent handling it.\n */\nexport function getMessageParticipantLabel(item: MessageParticipantSource): string {\n const platformIdentity = normalizeLabel(item.senderName) ?? normalizeLabel(item.senderEmail)\n const externalIdentity = normalizeLabel(item.externalName) ?? normalizeLabel(item.externalEmail)\n const preferred = isInboundExternalMessage(item) ? externalIdentity : platformIdentity\n const fallback = isInboundExternalMessage(item) ? platformIdentity : externalIdentity\n\n return preferred ?? fallback ?? item.senderUserId\n}\n\nexport function getMessageListParticipantLabel(\n item: MessageParticipantSource,\n folder: MessageFolder,\n t: Translate,\n): string {\n if ((folder === 'sent' || folder === 'drafts') && Number(item.recipientCount ?? 0) <= 0) {\n return t('messages.list.noRecipient', '(No recipient)')\n }\n\n return getMessageParticipantLabel(item)\n}\n"],
|
|
5
|
+
"mappings": "AAYA,MAAM,sCAAsC;AAmB5C,SAAS,eAAe,OAAiD;AACvE,QAAM,aAAa,OAAO,KAAK;AAC/B,SAAO,aAAa,aAAa;AACnC;AAEA,SAAS,yBAAyB,MAAyC;AACzE,SAAO,KAAK,qBAAqB;AACnC;AAoBO,SAAS,2BAA2B,MAAwC;AACjF,QAAM,mBAAmB,eAAe,KAAK,UAAU,KAAK,eAAe,KAAK,WAAW;AAC3F,QAAM,mBAAmB,eAAe,KAAK,YAAY,KAAK,eAAe,KAAK,aAAa;AAC/F,QAAM,YAAY,yBAAyB,IAAI,IAAI,mBAAmB;AACtE,QAAM,WAAW,yBAAyB,IAAI,IAAI,mBAAmB;AAErE,SAAO,aAAa,YAAY,KAAK;AACvC;AAEO,SAAS,+BACd,MACA,QACA,GACQ;AACR,OAAK,WAAW,UAAU,WAAW,aAAa,OAAO,KAAK,kBAAkB,CAAC,KAAK,GAAG;AACvF,WAAO,EAAE,6BAA6B,gBAAgB;AAAA,EACxD;AAEA,SAAO,2BAA2B,IAAI;AACxC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|