@open-mercato/core 0.6.6-develop.6314.1.c7b8291aa2 → 0.6.6-develop.6330.1.a261878aa8
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/customer_accounts/backend/customer_accounts/roles/[id]/page.js +7 -12
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +5 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -1
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +2 -1
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +2 -2
- package/dist/modules/customers/ai-tools/_shared.js +158 -0
- package/dist/modules/customers/ai-tools/_shared.js.map +7 -0
- package/dist/modules/customers/ai-tools/companies-pack.js +15 -121
- package/dist/modules/customers/ai-tools/companies-pack.js.map +2 -2
- package/dist/modules/customers/ai-tools/people-pack.js +9 -107
- package/dist/modules/customers/ai-tools/people-pack.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -0
- package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
- package/dist/modules/customers/components/calendar/EventPeekPopover.js +12 -1
- package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +2 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js +3 -0
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/types.js.map +1 -1
- package/dist/modules/customers/components/detail/EntityTagsDialog.js +76 -67
- package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +3 -3
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +4 -3
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/runs.js +1 -0
- package/dist/modules/data_sync/api/runs.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +3 -1
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/data/validators.js +1 -0
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +20 -0
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/entities/api/records.js +11 -1
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +17 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/messages/api/unread-count/route.js +32 -0
- package/dist/modules/messages/api/unread-count/route.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +13 -5
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/shipping_carriers/i18n/en.js +11 -1
- package/dist/modules/shipping_carriers/i18n/en.js.map +2 -2
- package/dist/modules/shipping_carriers/i18n/pl.js +11 -1
- package/dist/modules/shipping_carriers/i18n/pl.js.map +2 -2
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js +25 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js.map +7 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +3 -17
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +19 -14
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +7 -10
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +6 -7
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +3 -2
- package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +3 -2
- package/src/modules/customers/ai-tools/_shared.ts +270 -0
- package/src/modules/customers/ai-tools/companies-pack.ts +17 -157
- package/src/modules/customers/ai-tools/people-pack.ts +11 -133
- package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -0
- package/src/modules/customers/components/calendar/EventPeekPopover.tsx +29 -11
- package/src/modules/customers/components/calendar/TimeGrid.tsx +3 -0
- package/src/modules/customers/components/calendar/types.ts +1 -0
- package/src/modules/customers/components/detail/EntityTagsDialog.tsx +89 -71
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +6 -3
- package/src/modules/data_sync/api/runs.ts +1 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +3 -1
- package/src/modules/data_sync/data/validators.ts +1 -0
- package/src/modules/data_sync/i18n/de.json +1 -0
- package/src/modules/data_sync/i18n/en.json +1 -0
- package/src/modules/data_sync/i18n/es.json +1 -0
- package/src/modules/data_sync/i18n/pl.json +1 -0
- package/src/modules/data_sync/lib/sync-run-service.ts +23 -0
- package/src/modules/entities/api/records.ts +17 -1
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +25 -12
- package/src/modules/messages/api/unread-count/route.ts +40 -0
- package/src/modules/query_index/components/QueryIndexesTable.tsx +17 -15
- package/src/modules/shipping_carriers/i18n/de.json +11 -1
- package/src/modules/shipping_carriers/i18n/en.json +11 -1
- package/src/modules/shipping_carriers/i18n/en.ts +10 -0
- package/src/modules/shipping_carriers/i18n/es.json +11 -1
- package/src/modules/shipping_carriers/i18n/pl.json +11 -1
- package/src/modules/shipping_carriers/i18n/pl.ts +10 -0
- package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.tsx +35 -0
- package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +3 -22
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +30 -13
package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx"],
|
|
4
|
-
"sourcesContent": ["import type { InjectionColumnWidget } from '@open-mercato/shared/modules/widgets/injection'\
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\nimport type { InjectionColumnWidget } from '@open-mercato/shared/modules/widgets/injection'\nimport ShippingStatusBadgeWidget from './widget.client'\n\nconst widget: InjectionColumnWidget = {\n metadata: {\n id: 'shipping_carriers.injection.tracking-status-badge',\n priority: 45,\n },\n columns: [\n {\n id: 'carrier_status_badge',\n header: 'shipping_carriers.column.shippingStatus',\n accessorKey: '_carrier.status',\n sortable: false,\n cell: ({ getValue }) => {\n const value = getValue()\n if (typeof value !== 'string' || value.length === 0) return null\n return React.createElement(ShippingStatusBadgeWidget, { status: value })\n },\n },\n ],\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;AAEvB,OAAO,+BAA+B;AAEtC,MAAM,SAAgC;AAAA,EACpC,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,UAAU;AAAA,EACZ;AAAA,EACA,SAAS;AAAA,IACP;AAAA,MACE,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,UAAU;AAAA,MACV,MAAM,CAAC,EAAE,SAAS,MAAM;AACtB,cAAM,QAAQ,SAAS;AACvB,YAAI,OAAO,UAAU,YAAY,MAAM,WAAW,EAAG,QAAO;AAC5D,eAAO,MAAM,cAAc,2BAA2B,EAAE,QAAQ,MAAM,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,8 +3,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useParams } from "next/navigation";
|
|
5
5
|
import Link from "next/link";
|
|
6
|
+
import { useQuery } from "@tanstack/react-query";
|
|
6
7
|
import { ExternalLink, Languages } from "lucide-react";
|
|
7
8
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
9
|
+
import { hasAllFeatures } from "@open-mercato/shared/lib/auth/featureMatch";
|
|
10
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
8
11
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
9
12
|
import {
|
|
10
13
|
Drawer,
|
|
@@ -16,21 +19,23 @@ import {
|
|
|
16
19
|
} from "@open-mercato/ui/primitives/drawer";
|
|
17
20
|
import { TranslationManager } from "../../../components/TranslationManager.js";
|
|
18
21
|
import { extractRecordId } from "../../../lib/extract-record-id.js";
|
|
22
|
+
const TRANSLATION_ACCESS_FEATURES = ["translations.view"];
|
|
19
23
|
function useTranslationAccess() {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
const { data } = useQuery({
|
|
25
|
+
queryKey: ["translations", "widget-access", ...TRANSLATION_ACCESS_FEATURES],
|
|
26
|
+
queryFn: async () => {
|
|
27
|
+
const res = await apiCall("/api/auth/feature-check", {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "content-type": "application/json" },
|
|
30
|
+
body: JSON.stringify({ features: TRANSLATION_ACCESS_FEATURES })
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) return false;
|
|
33
|
+
return hasAllFeatures(TRANSLATION_ACCESS_FEATURES, res.result?.granted ?? []);
|
|
34
|
+
},
|
|
35
|
+
staleTime: 5 * 60 * 1e3,
|
|
36
|
+
retry: false
|
|
37
|
+
});
|
|
38
|
+
return data === true;
|
|
34
39
|
}
|
|
35
40
|
function TranslationWidget({ context, data }) {
|
|
36
41
|
const entityType = context?.entityId;
|
package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/translations/widgets/injection/translation-manager/widget.client.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useParams } from 'next/navigation'\nimport Link from 'next/link'\nimport { ExternalLink, Languages } from 'lucide-react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerDescription,\n DrawerHeader,\n DrawerTitle,\n} from '@open-mercato/ui/primitives/drawer'\nimport { TranslationManager } from '../../../components/TranslationManager'\nimport { extractRecordId } from '../../../lib/extract-record-id'\n\ntype WidgetContext = { entityId?: string; recordId?: string }\ntype WidgetData = Record<string, unknown> & { id?: string | number }\n\
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useParams } from 'next/navigation'\nimport Link from 'next/link'\nimport { useQuery } from '@tanstack/react-query'\nimport { ExternalLink, Languages } from 'lucide-react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { hasAllFeatures } from '@open-mercato/shared/lib/auth/featureMatch'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerDescription,\n DrawerHeader,\n DrawerTitle,\n} from '@open-mercato/ui/primitives/drawer'\nimport { TranslationManager } from '../../../components/TranslationManager'\nimport { extractRecordId } from '../../../lib/extract-record-id'\n\ntype WidgetContext = { entityId?: string; recordId?: string }\ntype WidgetData = Record<string, unknown> & { id?: string | number }\n\ntype FeatureCheckResponse = {\n ok: boolean\n granted: string[]\n userId: string\n}\n\nconst TRANSLATION_ACCESS_FEATURES = ['translations.view']\n\n// Probe access through the shared apiCall/React Query path so multiple injected\n// widget instances dedupe a single request via the query key. feature-check\n// always answers 200 with the granted feature list, so a user without\n// translations.view is hidden gracefully \u2014 no login redirect, no forbidden flash.\nfunction useTranslationAccess(): boolean {\n const { data } = useQuery<boolean>({\n queryKey: ['translations', 'widget-access', ...TRANSLATION_ACCESS_FEATURES],\n queryFn: async () => {\n const res = await apiCall<FeatureCheckResponse>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: TRANSLATION_ACCESS_FEATURES }),\n })\n if (!res.ok) return false\n return hasAllFeatures(TRANSLATION_ACCESS_FEATURES, res.result?.granted ?? [])\n },\n staleTime: 5 * 60 * 1000,\n retry: false,\n })\n return data === true\n}\n\nexport default function TranslationWidget({ context, data }: InjectionWidgetComponentProps<WidgetContext, WidgetData>) {\n const entityType = context?.entityId\n const params = useParams()\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const hasAccess = useTranslationAccess()\n\n const contextRecordId = typeof context?.recordId === 'string' && context.recordId.trim().length > 0\n ? context.recordId.trim()\n : undefined\n const dataRecordId = data?.id === undefined || data.id === null ? undefined : String(data.id)\n const routeRecordId = params ? extractRecordId(params as Record<string, string | string[]>) : undefined\n const recordId = contextRecordId ?? dataRecordId ?? routeRecordId\n const canRender = Boolean(entityType && recordId && hasAccess)\n\n if (!canRender) return null\n\n return (\n <Drawer open={open} onOpenChange={setOpen}>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setOpen(true)}\n aria-label={t('translations.widgets.translationManager.fullManager', 'Translation manager')}\n title={t('translations.widgets.translationManager.fullManager', 'Translation manager')}\n >\n <Languages className=\"size-4\" />\n </Button>\n <DrawerContent className=\"max-w-4xl\">\n <DrawerHeader>\n <DrawerTitle>\n {t('translations.widgets.translationManager.groupLabel', 'Translations')}\n </DrawerTitle>\n <DrawerDescription>\n {t(\n 'translations.widgets.translationManager.groupDescription',\n 'Manage translations for this record across supported locales.',\n )}\n </DrawerDescription>\n </DrawerHeader>\n <DrawerBody>\n <TranslationManager\n mode=\"embedded\"\n compact\n entityType={entityType}\n recordId={recordId}\n baseValues={data}\n />\n </DrawerBody>\n {/* Custom footer: 2 inline link affordances (not action buttons),\n wrap-friendly + left-aligned. DS DrawerFooter layouts assume\n buttons in the trailing slot, so we hand-roll the row with\n the DrawerFooter padding convention (`px-6 pt-4 pb-5`). */}\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 px-6 pt-4 pb-5\">\n <Link\n href={`/backend/entities/system/${encodeURIComponent(entityType!)}`}\n className=\"inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors\"\n >\n <Languages className=\"size-3\" />\n {t('translations.widgets.translationManager.customFieldLabels', 'Custom fields translations')}\n <ExternalLink className=\"size-2.5\" />\n </Link>\n <Link\n href=\"/backend/config/translations\"\n className=\"inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors\"\n >\n <Languages className=\"size-3\" />\n {t('translations.widgets.translationManager.fullManager', 'Translation manager')}\n <ExternalLink className=\"size-2.5\" />\n </Link>\n </div>\n </DrawerContent>\n </Drawer>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6330.1.a261878aa8",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -246,16 +246,16 @@
|
|
|
246
246
|
"zod": "^4.4.3"
|
|
247
247
|
},
|
|
248
248
|
"peerDependencies": {
|
|
249
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
250
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
251
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
249
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6330.1.a261878aa8",
|
|
250
|
+
"@open-mercato/shared": "0.6.6-develop.6330.1.a261878aa8",
|
|
251
|
+
"@open-mercato/ui": "0.6.6-develop.6330.1.a261878aa8",
|
|
252
252
|
"react": "^19.0.0",
|
|
253
253
|
"react-dom": "^19.0.0"
|
|
254
254
|
},
|
|
255
255
|
"devDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
257
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
258
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6330.1.a261878aa8",
|
|
257
|
+
"@open-mercato/shared": "0.6.6-develop.6330.1.a261878aa8",
|
|
258
|
+
"@open-mercato/ui": "0.6.6-develop.6330.1.a261878aa8",
|
|
259
259
|
"@testing-library/dom": "^10.4.1",
|
|
260
260
|
"@testing-library/jest-dom": "^6.9.1",
|
|
261
261
|
"@testing-library/react": "^16.3.1",
|
|
@@ -7,6 +7,7 @@ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
|
7
7
|
import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
|
|
8
8
|
import type { CrudField, CrudFormGroup, CrudFormGroupComponentProps } from '@open-mercato/ui/backend/CrudForm'
|
|
9
9
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
10
|
+
import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
|
|
10
11
|
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
11
12
|
import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
12
13
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
@@ -106,12 +107,9 @@ function PortalPermissionsEditor({ values, setValue }: CrudFormGroupComponentPro
|
|
|
106
107
|
<div className="flex items-center justify-between border-b px-4 py-3">
|
|
107
108
|
<span className="text-sm font-semibold">{t(group.labelKey, group.fallback)}</span>
|
|
108
109
|
<label className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
ref={(el) => { if (el) el.indeterminate = someSelected && !allSelected }}
|
|
113
|
-
onChange={() => handleGroupToggle(groupFeatures)}
|
|
114
|
-
className="rounded border-border"
|
|
110
|
+
<Checkbox
|
|
111
|
+
checked={allSelected ? true : (someSelected ? 'indeterminate' : false)}
|
|
112
|
+
onCheckedChange={() => handleGroupToggle(groupFeatures)}
|
|
115
113
|
/>
|
|
116
114
|
{t('customer_accounts.admin.roleDetail.selectAll', 'Select all')}
|
|
117
115
|
</label>
|
|
@@ -121,11 +119,10 @@ function PortalPermissionsEditor({ values, setValue }: CrudFormGroupComponentPro
|
|
|
121
119
|
const feature = PORTAL_FEATURES.find((portalFeature) => portalFeature.id === featureId)
|
|
122
120
|
return (
|
|
123
121
|
<label key={featureId} className="flex items-start gap-3 px-4 py-3 cursor-pointer hover:bg-muted/50 transition-colors">
|
|
124
|
-
<
|
|
125
|
-
type="checkbox"
|
|
122
|
+
<Checkbox
|
|
126
123
|
checked={features.includes(featureId)}
|
|
127
|
-
|
|
128
|
-
className="mt-0.5
|
|
124
|
+
onCheckedChange={() => handleFeatureToggle(featureId)}
|
|
125
|
+
className="mt-0.5"
|
|
129
126
|
/>
|
|
130
127
|
<div className="space-y-0.5">
|
|
131
128
|
<div className="text-sm font-medium">{feature ? t(feature.labelKey, feature.fallback) : featureId}</div>
|
|
@@ -191,20 +191,19 @@ function CreateUserDialog({
|
|
|
191
191
|
{roleOptions.map((role) => {
|
|
192
192
|
const isSelected = selectedRoleIds.includes(role.id)
|
|
193
193
|
return (
|
|
194
|
-
<
|
|
194
|
+
<Button
|
|
195
195
|
key={role.id}
|
|
196
196
|
type="button"
|
|
197
|
+
size="2xs"
|
|
198
|
+
variant={isSelected ? 'secondary' : 'outline'}
|
|
199
|
+
aria-pressed={isSelected}
|
|
197
200
|
onClick={() => setSelectedRoleIds((prev) =>
|
|
198
201
|
prev.includes(role.id) ? prev.filter((rid) => rid !== role.id) : [...prev, role.id],
|
|
199
202
|
)}
|
|
200
|
-
className=
|
|
201
|
-
isSelected
|
|
202
|
-
? 'border-primary bg-primary/10 text-primary'
|
|
203
|
-
: 'border-border bg-background text-muted-foreground hover:bg-muted'
|
|
204
|
-
}`}
|
|
203
|
+
className="rounded-full"
|
|
205
204
|
>
|
|
206
205
|
{role.label}
|
|
207
|
-
</
|
|
206
|
+
</Button>
|
|
208
207
|
)
|
|
209
208
|
})}
|
|
210
209
|
</div>
|
|
@@ -8,6 +8,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
|
8
8
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
9
9
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
10
10
|
import { EmailInput } from '@open-mercato/ui/primitives/email-input'
|
|
11
|
+
import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
|
|
11
12
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
12
13
|
|
|
13
14
|
interface AccountStatusData {
|
|
@@ -305,9 +306,9 @@ export default function AccountStatusWidget({ context }: AccountStatusProps) {
|
|
|
305
306
|
<div className="space-y-1 text-sm">
|
|
306
307
|
<div className="flex justify-between">
|
|
307
308
|
<span className="text-muted-foreground">{t('common.status', 'Status')}</span>
|
|
308
|
-
<
|
|
309
|
+
<StatusBadge variant={data.isActive ? 'success' : 'error'} dot>
|
|
309
310
|
{data.isActive ? t('common.active', 'Active') : t('common.inactive', 'Inactive')}
|
|
310
|
-
</
|
|
311
|
+
</StatusBadge>
|
|
311
312
|
</div>
|
|
312
313
|
<div className="flex justify-between">
|
|
313
314
|
<span className="text-muted-foreground">{t('common.email', 'Email')}</span>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useQuery } from '@tanstack/react-query'
|
|
4
4
|
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
5
5
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
6
|
+
import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
|
|
6
7
|
|
|
7
8
|
interface CompanyUser {
|
|
8
9
|
id: string
|
|
@@ -59,9 +60,9 @@ export default function CompanyUsersWidget({ context }: CompanyUsersProps) {
|
|
|
59
60
|
<div className="text-xs text-muted-foreground">{user.email}</div>
|
|
60
61
|
</div>
|
|
61
62
|
<div className="flex items-center gap-2">
|
|
62
|
-
<
|
|
63
|
+
<StatusBadge variant={user.isActive ? 'success' : 'error'} dot>
|
|
63
64
|
{user.isActive ? t('common.active', 'Active') : t('common.inactive', 'Inactive')}
|
|
64
|
-
</
|
|
65
|
+
</StatusBadge>
|
|
65
66
|
<a
|
|
66
67
|
href={`/backend/customer_accounts/users/${user.id}`}
|
|
67
68
|
className="text-xs text-primary hover:underline"
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for customers AI tool packs.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the catalog precedent (`packages/core/src/modules/catalog/ai-tools/_shared.ts`):
|
|
5
|
+
* the company and people packs previously carried byte-for-byte-identical copies
|
|
6
|
+
* of the date-to-ISO helper, the `resolveEm` / `buildScope` accessors, the
|
|
7
|
+
* list-row → summary mapper, and the related-records builder
|
|
8
|
+
* (addresses / activities / notes / tasks / interactions / tags / deals, plus a
|
|
9
|
+
* companies-only `people` mapper). Centralizing them here gives both packs one
|
|
10
|
+
* source of truth so a change to the related-records output shape no longer has
|
|
11
|
+
* to be applied — and kept in sync — in two places.
|
|
12
|
+
*
|
|
13
|
+
* This is a pure internal refactor: tool names, input schemas, `requiredFeatures`,
|
|
14
|
+
* and emitted output shapes stay identical.
|
|
15
|
+
*/
|
|
16
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
17
|
+
import type { AiToolExecutionContext } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'
|
|
18
|
+
import type { CustomersToolContext } from './types'
|
|
19
|
+
|
|
20
|
+
export function toIso(value: unknown): string | null {
|
|
21
|
+
if (!value) return null
|
|
22
|
+
const dt = value instanceof Date ? value : new Date(String(value))
|
|
23
|
+
if (Number.isNaN(dt.getTime())) return null
|
|
24
|
+
return dt.toISOString()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveEm(ctx: CustomersToolContext | AiToolExecutionContext): EntityManager {
|
|
28
|
+
return ctx.container.resolve<EntityManager>('em')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildScope(ctx: CustomersToolContext | AiToolExecutionContext, tenantId: string) {
|
|
32
|
+
return {
|
|
33
|
+
tenantId,
|
|
34
|
+
organizationId: ctx.organizationId,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* -------------------------------------------------------------------------- */
|
|
39
|
+
/* List-row summary mapper */
|
|
40
|
+
/* -------------------------------------------------------------------------- */
|
|
41
|
+
|
|
42
|
+
export type CustomerListApiItemBase = {
|
|
43
|
+
id?: string
|
|
44
|
+
display_name?: string | null
|
|
45
|
+
displayName?: string | null
|
|
46
|
+
primary_email?: string | null
|
|
47
|
+
primaryEmail?: string | null
|
|
48
|
+
primary_phone?: string | null
|
|
49
|
+
primaryPhone?: string | null
|
|
50
|
+
status?: string | null
|
|
51
|
+
lifecycle_stage?: string | null
|
|
52
|
+
lifecycleStage?: string | null
|
|
53
|
+
source?: string | null
|
|
54
|
+
owner_user_id?: string | null
|
|
55
|
+
ownerUserId?: string | null
|
|
56
|
+
organization_id?: string | null
|
|
57
|
+
organizationId?: string | null
|
|
58
|
+
tenant_id?: string | null
|
|
59
|
+
tenantId?: string | null
|
|
60
|
+
created_at?: string | null
|
|
61
|
+
createdAt?: string | null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type CustomerListSummary = {
|
|
65
|
+
id: string | undefined
|
|
66
|
+
displayName: string | null
|
|
67
|
+
primaryEmail: string | null
|
|
68
|
+
primaryPhone: string | null
|
|
69
|
+
status: string | null
|
|
70
|
+
lifecycleStage: string | null
|
|
71
|
+
source: string | null
|
|
72
|
+
ownerUserId: string | null
|
|
73
|
+
organizationId: string | null
|
|
74
|
+
tenantId: string | null
|
|
75
|
+
createdAt: string | null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function toCustomerListSummary(row: CustomerListApiItemBase): CustomerListSummary {
|
|
79
|
+
const createdAtRaw = row.created_at ?? row.createdAt ?? null
|
|
80
|
+
const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null
|
|
81
|
+
return {
|
|
82
|
+
id: row.id,
|
|
83
|
+
displayName: row.display_name ?? row.displayName ?? null,
|
|
84
|
+
primaryEmail: row.primary_email ?? row.primaryEmail ?? null,
|
|
85
|
+
primaryPhone: row.primary_phone ?? row.primaryPhone ?? null,
|
|
86
|
+
status: row.status ?? null,
|
|
87
|
+
lifecycleStage: row.lifecycle_stage ?? row.lifecycleStage ?? null,
|
|
88
|
+
source: row.source ?? null,
|
|
89
|
+
ownerUserId: row.owner_user_id ?? row.ownerUserId ?? null,
|
|
90
|
+
organizationId: row.organization_id ?? row.organizationId ?? null,
|
|
91
|
+
tenantId: row.tenant_id ?? row.tenantId ?? null,
|
|
92
|
+
createdAt,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* -------------------------------------------------------------------------- */
|
|
97
|
+
/* Related-records mappers */
|
|
98
|
+
/* -------------------------------------------------------------------------- */
|
|
99
|
+
|
|
100
|
+
function asRows(value: unknown): Array<Record<string, unknown>> {
|
|
101
|
+
return Array.isArray(value) ? (value as Array<Record<string, unknown>>) : []
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function mapAddresses(rows: Array<Record<string, unknown>>) {
|
|
105
|
+
return rows.map((address) => ({
|
|
106
|
+
id: address.id,
|
|
107
|
+
name: address.name ?? null,
|
|
108
|
+
purpose: address.purpose ?? null,
|
|
109
|
+
addressLine1: address.addressLine1 ?? null,
|
|
110
|
+
addressLine2: address.addressLine2 ?? null,
|
|
111
|
+
city: address.city ?? null,
|
|
112
|
+
region: address.region ?? null,
|
|
113
|
+
postalCode: address.postalCode ?? null,
|
|
114
|
+
country: address.country ?? null,
|
|
115
|
+
isPrimary: !!address.isPrimary,
|
|
116
|
+
}))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function mapActivities(rows: Array<Record<string, unknown>>) {
|
|
120
|
+
return rows.map((activity) => ({
|
|
121
|
+
id: activity.id,
|
|
122
|
+
activityType: activity.activityType,
|
|
123
|
+
subject: activity.subject ?? null,
|
|
124
|
+
body: activity.body ?? null,
|
|
125
|
+
occurredAt: toIso(activity.occurredAt),
|
|
126
|
+
createdAt: toIso(activity.createdAt),
|
|
127
|
+
}))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function mapNotes(rows: Array<Record<string, unknown>>) {
|
|
131
|
+
return rows.map((comment) => ({
|
|
132
|
+
id: comment.id,
|
|
133
|
+
body: comment.body,
|
|
134
|
+
authorUserId: comment.authorUserId ?? null,
|
|
135
|
+
createdAt: toIso(comment.createdAt),
|
|
136
|
+
}))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function mapTasks(rows: Array<Record<string, unknown>>) {
|
|
140
|
+
return rows.map((task) => ({
|
|
141
|
+
id: task.id,
|
|
142
|
+
todoId: task.todoId ?? task.id,
|
|
143
|
+
todoSource: task.todoSource ?? null,
|
|
144
|
+
createdAt: toIso(task.createdAt),
|
|
145
|
+
}))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function mapInteractions(rows: Array<Record<string, unknown>>) {
|
|
149
|
+
return rows.map((interaction) => ({
|
|
150
|
+
id: interaction.id,
|
|
151
|
+
interactionType: interaction.interactionType,
|
|
152
|
+
title: interaction.title ?? null,
|
|
153
|
+
status: interaction.status,
|
|
154
|
+
scheduledAt: toIso(interaction.scheduledAt),
|
|
155
|
+
occurredAt: toIso(interaction.occurredAt),
|
|
156
|
+
}))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type RelatedTag = { id: string; slug: string; label: string; color: string | null }
|
|
160
|
+
|
|
161
|
+
export function mapTags(rows: Array<Record<string, unknown>>): RelatedTag[] {
|
|
162
|
+
return rows
|
|
163
|
+
.map((tag) => {
|
|
164
|
+
if (!tag || typeof tag !== 'object') return null
|
|
165
|
+
const id = typeof tag.id === 'string' ? tag.id : null
|
|
166
|
+
const label = typeof tag.label === 'string' ? tag.label : null
|
|
167
|
+
if (!id || !label) return null
|
|
168
|
+
const slug = typeof tag.slug === 'string' ? tag.slug : label
|
|
169
|
+
const color = typeof tag.color === 'string' ? tag.color : null
|
|
170
|
+
return { id, slug, label, color }
|
|
171
|
+
})
|
|
172
|
+
.filter((entry): entry is RelatedTag => entry !== null)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export type RelatedDeal = {
|
|
176
|
+
id: string
|
|
177
|
+
title: string
|
|
178
|
+
status: string | null
|
|
179
|
+
pipelineStageId: string | null
|
|
180
|
+
valueAmount: string | null
|
|
181
|
+
valueCurrency: string | null
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function mapDeals(rows: Array<Record<string, unknown>>): RelatedDeal[] {
|
|
185
|
+
return rows
|
|
186
|
+
.map((deal) => {
|
|
187
|
+
if (!deal || typeof deal !== 'object') return null
|
|
188
|
+
const id = typeof deal.id === 'string' ? deal.id : null
|
|
189
|
+
if (!id) return null
|
|
190
|
+
return {
|
|
191
|
+
id,
|
|
192
|
+
title: typeof deal.title === 'string' ? deal.title : '',
|
|
193
|
+
status: typeof deal.status === 'string' ? deal.status : null,
|
|
194
|
+
pipelineStageId: typeof deal.pipelineStageId === 'string' ? deal.pipelineStageId : null,
|
|
195
|
+
valueAmount:
|
|
196
|
+
typeof deal.valueAmount === 'string'
|
|
197
|
+
? deal.valueAmount
|
|
198
|
+
: deal.valueAmount === null || deal.valueAmount === undefined
|
|
199
|
+
? null
|
|
200
|
+
: String(deal.valueAmount),
|
|
201
|
+
valueCurrency: typeof deal.valueCurrency === 'string' ? deal.valueCurrency : null,
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
.filter((value): value is RelatedDeal => value !== null)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export type RelatedPerson = {
|
|
208
|
+
id: string
|
|
209
|
+
displayName: string
|
|
210
|
+
primaryEmail: string | null
|
|
211
|
+
primaryPhone: string | null
|
|
212
|
+
jobTitle: string | null
|
|
213
|
+
department: string | null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function mapPeople(rows: Array<Record<string, unknown>>): RelatedPerson[] {
|
|
217
|
+
return rows
|
|
218
|
+
.map((person) => {
|
|
219
|
+
if (!person || typeof person !== 'object') return null
|
|
220
|
+
const id = typeof person.id === 'string' ? person.id : null
|
|
221
|
+
const displayName = typeof person.displayName === 'string' ? person.displayName : null
|
|
222
|
+
if (!id || !displayName) return null
|
|
223
|
+
return {
|
|
224
|
+
id,
|
|
225
|
+
displayName,
|
|
226
|
+
primaryEmail: typeof person.primaryEmail === 'string' ? person.primaryEmail : null,
|
|
227
|
+
primaryPhone: typeof person.primaryPhone === 'string' ? person.primaryPhone : null,
|
|
228
|
+
jobTitle: typeof person.jobTitle === 'string' ? person.jobTitle : null,
|
|
229
|
+
department: typeof person.department === 'string' ? person.department : null,
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
.filter((value): value is RelatedPerson => value !== null)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export type CustomerRelatedRecords = {
|
|
236
|
+
addresses: ReturnType<typeof mapAddresses>
|
|
237
|
+
activities: ReturnType<typeof mapActivities>
|
|
238
|
+
notes: ReturnType<typeof mapNotes>
|
|
239
|
+
tasks: ReturnType<typeof mapTasks>
|
|
240
|
+
interactions: ReturnType<typeof mapInteractions>
|
|
241
|
+
tags: RelatedTag[]
|
|
242
|
+
deals: RelatedDeal[]
|
|
243
|
+
people?: RelatedPerson[]
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Builds the related-records block from a customers detail API payload
|
|
248
|
+
* (`addresses`, `activities`, `comments`, `todos`, `interactions`, `tags`,
|
|
249
|
+
* `deals`, and — for companies — `people`). The key order matches the
|
|
250
|
+
* pre-refactor literals so the emitted shape is unchanged. Pass
|
|
251
|
+
* `includePeople: true` to add the companies-only `people` collection.
|
|
252
|
+
*/
|
|
253
|
+
export function buildRelatedRecords(
|
|
254
|
+
data: Record<string, unknown>,
|
|
255
|
+
options: { includePeople?: boolean } = {},
|
|
256
|
+
): CustomerRelatedRecords {
|
|
257
|
+
const related: CustomerRelatedRecords = {
|
|
258
|
+
addresses: mapAddresses(asRows(data.addresses)),
|
|
259
|
+
activities: mapActivities(asRows(data.activities)),
|
|
260
|
+
notes: mapNotes(asRows(data.comments)),
|
|
261
|
+
tasks: mapTasks(asRows(data.todos)),
|
|
262
|
+
interactions: mapInteractions(asRows(data.interactions)),
|
|
263
|
+
tags: mapTags(asRows(data.tags)),
|
|
264
|
+
deals: mapDeals(asRows(data.deals)),
|
|
265
|
+
}
|
|
266
|
+
if (options.includePeople) {
|
|
267
|
+
related.people = mapPeople(asRows(data.people))
|
|
268
|
+
}
|
|
269
|
+
return related
|
|
270
|
+
}
|