@open-mercato/core 0.5.1-develop.2802.9223828f7f → 0.5.1-develop.2851.2854b4507f

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.
Files changed (79) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/action_log/index.js +4 -0
  3. package/dist/generated/entities/action_log/index.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +2 -0
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/modules/audit_logs/data/entities.js +10 -1
  7. package/dist/modules/audit_logs/data/entities.js.map +2 -2
  8. package/dist/modules/audit_logs/data/validators.js +2 -0
  9. package/dist/modules/audit_logs/data/validators.js.map +2 -2
  10. package/dist/modules/audit_logs/migrations/Migration20260423202109.js +15 -0
  11. package/dist/modules/audit_logs/migrations/Migration20260423202109.js.map +7 -0
  12. package/dist/modules/audit_logs/services/accessLogService.js +3 -2
  13. package/dist/modules/audit_logs/services/accessLogService.js.map +3 -3
  14. package/dist/modules/audit_logs/services/actionLogService.js +13 -2
  15. package/dist/modules/audit_logs/services/actionLogService.js.map +3 -3
  16. package/dist/modules/customers/api/entity-roles-factory.js +3 -18
  17. package/dist/modules/customers/api/entity-roles-factory.js.map +2 -2
  18. package/dist/modules/customers/api/interactions/cancel/route.js +7 -2
  19. package/dist/modules/customers/api/interactions/cancel/route.js.map +2 -2
  20. package/dist/modules/customers/api/interactions/complete/route.js +7 -2
  21. package/dist/modules/customers/api/interactions/complete/route.js.map +2 -2
  22. package/dist/modules/customers/backend/customers/deals/page.js +45 -44
  23. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  24. package/dist/modules/customers/commands/comments.js +6 -0
  25. package/dist/modules/customers/commands/comments.js.map +2 -2
  26. package/dist/modules/customers/components/detail/AssignRoleDialog.js +41 -13
  27. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
  28. package/dist/modules/customers/components/detail/CompanyDetailHeader.js +30 -0
  29. package/dist/modules/customers/components/detail/CompanyDetailHeader.js.map +2 -2
  30. package/dist/modules/customers/components/detail/DealDetailHeader.js +32 -0
  31. package/dist/modules/customers/components/detail/DealDetailHeader.js.map +2 -2
  32. package/dist/modules/customers/components/detail/DealWonPopup.js +2 -2
  33. package/dist/modules/customers/components/detail/DealWonPopup.js.map +2 -2
  34. package/dist/modules/customers/components/detail/InlineActivityComposer.js +62 -6
  35. package/dist/modules/customers/components/detail/InlineActivityComposer.js.map +2 -2
  36. package/dist/modules/customers/components/detail/ObjectHistoryButton.js +39 -0
  37. package/dist/modules/customers/components/detail/ObjectHistoryButton.js.map +7 -0
  38. package/dist/modules/customers/components/detail/PersonDetailHeader.js +30 -0
  39. package/dist/modules/customers/components/detail/PersonDetailHeader.js.map +2 -2
  40. package/dist/modules/customers/components/detail/RolesSection.js +14 -4
  41. package/dist/modules/customers/components/detail/RolesSection.js.map +3 -3
  42. package/dist/modules/customers/components/formConfig.js +16 -2
  43. package/dist/modules/customers/components/formConfig.js.map +2 -2
  44. package/dist/modules/customers/lib/displayName.js +15 -0
  45. package/dist/modules/customers/lib/displayName.js.map +7 -0
  46. package/dist/modules/customers/lib/interactionReadModel.js +1 -2
  47. package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
  48. package/dist/modules/customers/lib/operationMetadata.js +21 -0
  49. package/dist/modules/customers/lib/operationMetadata.js.map +7 -0
  50. package/generated/entities/action_log/index.ts +2 -0
  51. package/generated/entity-fields-registry.ts +2 -0
  52. package/package.json +3 -3
  53. package/src/modules/audit_logs/data/entities.ts +7 -0
  54. package/src/modules/audit_logs/data/validators.ts +2 -0
  55. package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +51 -5
  56. package/src/modules/audit_logs/migrations/Migration20260423202109.ts +15 -0
  57. package/src/modules/audit_logs/services/accessLogService.ts +1 -3
  58. package/src/modules/audit_logs/services/actionLogService.ts +11 -6
  59. package/src/modules/customers/api/entity-roles-factory.ts +3 -23
  60. package/src/modules/customers/api/interactions/cancel/route.ts +7 -2
  61. package/src/modules/customers/api/interactions/complete/route.ts +7 -2
  62. package/src/modules/customers/backend/customers/deals/page.tsx +48 -44
  63. package/src/modules/customers/commands/comments.ts +6 -0
  64. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +37 -9
  65. package/src/modules/customers/components/detail/CompanyDetailHeader.tsx +25 -0
  66. package/src/modules/customers/components/detail/DealDetailHeader.tsx +29 -0
  67. package/src/modules/customers/components/detail/DealWonPopup.tsx +2 -2
  68. package/src/modules/customers/components/detail/InlineActivityComposer.tsx +65 -6
  69. package/src/modules/customers/components/detail/ObjectHistoryButton.tsx +47 -0
  70. package/src/modules/customers/components/detail/PersonDetailHeader.tsx +25 -0
  71. package/src/modules/customers/components/detail/RolesSection.tsx +20 -1
  72. package/src/modules/customers/components/formConfig.tsx +14 -2
  73. package/src/modules/customers/i18n/de.json +12 -0
  74. package/src/modules/customers/i18n/en.json +12 -0
  75. package/src/modules/customers/i18n/es.json +13 -1
  76. package/src/modules/customers/i18n/pl.json +13 -1
  77. package/src/modules/customers/lib/displayName.ts +16 -0
  78. package/src/modules/customers/lib/interactionReadModel.ts +1 -7
  79. package/src/modules/customers/lib/operationMetadata.ts +38 -0
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Search, Check } from "lucide-react";
4
+ import Link from "next/link";
5
+ import { Search, Check, Settings2 } from "lucide-react";
5
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
7
  import { Button } from "@open-mercato/ui/primitives/button";
7
8
  import { Badge } from "@open-mercato/ui/primitives/badge";
@@ -15,6 +16,7 @@ import {
15
16
  } from "@open-mercato/ui/primitives/dialog";
16
17
  import { fetchAssignableStaffMembersPage } from "./assignableStaff.js";
17
18
  import { getInitials } from "./utils.js";
19
+ const MANAGE_ROLE_TYPES_HREF = "/backend/config/customers";
18
20
  const ASSIGNABLE_STAFF_PAGE_SIZE = 24;
19
21
  function AssignRoleDialog({
20
22
  open,
@@ -24,7 +26,8 @@ function AssignRoleDialog({
24
26
  entityName,
25
27
  existingRoleTypes,
26
28
  existingAssignments = [],
27
- initialRoleType = null
29
+ initialRoleType = null,
30
+ canManageRoleTypes = false
28
31
  }) {
29
32
  const t = useT();
30
33
  const [step, setStep] = React.useState(1);
@@ -331,7 +334,19 @@ function AssignRoleDialog({
331
334
  children: t("customers.roles.dialog.sourceBadge.dictionary", "Dictionary")
332
335
  }
333
336
  ) : null
334
- ] })
337
+ ] }),
338
+ canManageRoleTypes ? /* @__PURE__ */ jsxs(
339
+ Link,
340
+ {
341
+ href: MANAGE_ROLE_TYPES_HREF,
342
+ className: "mt-3 inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:underline",
343
+ "data-testid": "assign-role-dialog-manage-role-types",
344
+ children: [
345
+ /* @__PURE__ */ jsx(Settings2, { className: "size-3.5", "aria-hidden": "true" }),
346
+ t("customers.roles.dialog.manageRoleTypes", "Manage role types")
347
+ ]
348
+ }
349
+ ) : null
335
350
  ] }),
336
351
  !availableRoleTypes.length ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed border-border/80 px-4 py-6 text-sm text-muted-foreground", children: t(
337
352
  "customers.roles.dialog.noAvailableRoles",
@@ -354,16 +369,29 @@ function AssignRoleDialog({
354
369
  )
355
370
  ] })
356
371
  ] }),
357
- /* @__PURE__ */ jsx(
358
- Button,
359
- {
360
- type: "button",
361
- variant: "outline",
362
- size: "sm",
363
- onClick: () => setStep(1),
364
- children: t("customers.roles.dialog.change", "Change")
365
- }
366
- )
372
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
373
+ canManageRoleTypes ? /* @__PURE__ */ jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: /* @__PURE__ */ jsxs(
374
+ Link,
375
+ {
376
+ href: MANAGE_ROLE_TYPES_HREF,
377
+ "data-testid": "assign-role-dialog-manage-role-types-step2",
378
+ children: [
379
+ /* @__PURE__ */ jsx(Settings2, { className: "mr-1 size-3.5", "aria-hidden": "true" }),
380
+ t("customers.roles.dialog.manageRoleTypes", "Manage role types")
381
+ ]
382
+ }
383
+ ) }) : null,
384
+ /* @__PURE__ */ jsx(
385
+ Button,
386
+ {
387
+ type: "button",
388
+ variant: "outline",
389
+ size: "sm",
390
+ onClick: () => setStep(1),
391
+ children: t("customers.roles.dialog.change", "Change")
392
+ }
393
+ )
394
+ ] })
367
395
  ] }) }),
368
396
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
369
397
  /* @__PURE__ */ jsx("p", { className: "text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground", children: t("customers.roles.dialog.teamLabel", "Select a team member") }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/AssignRoleDialog.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Search, Check } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport type { DictionaryEntryOption } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'\nimport type { RoleAssignment } from './RoleAssignmentRow'\nimport { fetchAssignableStaffMembersPage } from './assignableStaff'\nimport { getInitials } from './utils'\n\ntype StaffMember = {\n id: string\n displayName: string\n email: string | null\n teamName: string | null\n}\n\ninterface AssignRoleDialogProps {\n open: boolean\n onClose: () => void\n onAssign: (roleType: string, userId: string) => Promise<void>\n roleTypes: DictionaryEntryOption[]\n entityName: string\n existingRoleTypes?: Set<string>\n existingAssignments?: RoleAssignment[]\n initialRoleType?: string | null\n}\n\ntype StepId = 1 | 2 | 3\n\ntype TeamFilter = {\n id: string\n label: string\n count: number\n}\n\nconst ASSIGNABLE_STAFF_PAGE_SIZE = 24\n\nexport function AssignRoleDialog({\n open,\n onClose,\n onAssign,\n roleTypes,\n entityName,\n existingRoleTypes,\n existingAssignments = [],\n initialRoleType = null,\n}: AssignRoleDialogProps) {\n const t = useT()\n const [step, setStep] = React.useState<StepId>(1)\n const [selectedRoleType, setSelectedRoleType] = React.useState('')\n const [selectedUser, setSelectedUser] = React.useState<StaffMember | null>(null)\n const [searchQuery, setSearchQuery] = React.useState('')\n const [users, setUsers] = React.useState<StaffMember[]>([])\n const [loading, setLoading] = React.useState(false)\n const [loadingMore, setLoadingMore] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [activeTeam, setActiveTeam] = React.useState('all')\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [totalUsers, setTotalUsers] = React.useState(0)\n const [currentPage, setCurrentPage] = React.useState(1)\n const deferredSearchQuery = React.useDeferredValue(searchQuery)\n const requestSequenceRef = React.useRef(0)\n\n const availableRoleTypes = React.useMemo(\n () => roleTypes.filter((roleType) => !existingRoleTypes?.has(roleType.value)),\n [existingRoleTypes, roleTypes],\n )\n\n React.useEffect(() => {\n if (!open) {\n setStep(1)\n setSelectedRoleType('')\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n return\n }\n const resolvedInitialRoleType =\n typeof initialRoleType === 'string' && initialRoleType.trim().length > 0\n ? initialRoleType.trim()\n : ''\n setStep(resolvedInitialRoleType ? 2 : 1)\n setSelectedRoleType(resolvedInitialRoleType)\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n }, [initialRoleType, open])\n\n const searchUsers = React.useCallback(\n async ({\n query,\n page,\n append,\n }: {\n query: string\n page: number\n append: boolean\n }) => {\n const requestId = append ? requestSequenceRef.current : requestSequenceRef.current + 1\n requestSequenceRef.current = requestId\n\n if (append) {\n setLoadingMore(true)\n } else {\n setLoading(true)\n }\n\n try {\n const result = await fetchAssignableStaffMembersPage(query, {\n page,\n pageSize: ASSIGNABLE_STAFF_PAGE_SIZE,\n })\n if (requestSequenceRef.current !== requestId) return\n\n const nextUsers = result.items.map((member) => ({\n id: member.userId,\n displayName: member.displayName,\n email: member.email,\n teamName: member.teamName,\n }))\n\n setUsers((current) => {\n if (!append) return nextUsers\n const merged = new Map(current.map((user) => [user.id, user]))\n nextUsers.forEach((user) => merged.set(user.id, user))\n return Array.from(merged.values())\n })\n setTotalUsers(result.total)\n setCurrentPage(result.page)\n setLoadError(null)\n } catch {\n if (requestSequenceRef.current !== requestId) return\n if (!append) {\n setUsers([])\n setTotalUsers(0)\n setCurrentPage(1)\n }\n setLoadError(\n t(\n 'customers.assignableStaff.loadError',\n 'Unable to load team members. Check your permissions and try again.',\n ),\n )\n } finally {\n if (requestSequenceRef.current !== requestId) return\n if (append) {\n setLoadingMore(false)\n } else {\n setLoading(false)\n }\n }\n },\n [t],\n )\n\n React.useEffect(() => {\n if (step >= 2) {\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({ query: deferredSearchQuery, page: 1, append: false }).catch(() => {})\n }\n }, [deferredSearchQuery, searchUsers, step])\n\n const handleLoadMore = React.useCallback(() => {\n if (loading || loadingMore || users.length >= totalUsers) return\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({\n query: deferredSearchQuery,\n page: currentPage + 1,\n append: true,\n }).catch(() => {})\n }, [currentPage, deferredSearchQuery, loading, loadingMore, searchUsers, totalUsers, users.length])\n\n const selectedRole = React.useMemo(\n () => roleTypes.find((roleType) => roleType.value === selectedRoleType) ?? null,\n [roleTypes, selectedRoleType],\n )\n\n const roleTypeLabelMap = React.useMemo(\n () => new Map(roleTypes.map((roleType) => [roleType.value, roleType.label])),\n [roleTypes],\n )\n\n const conflictsByUserId = React.useMemo(() => {\n const next = new Map<string, string[]>()\n existingAssignments.forEach((assignment) => {\n if (!assignment.userId) return\n if (assignment.roleType === selectedRoleType) return\n const label = roleTypeLabelMap.get(assignment.roleType) ?? assignment.roleType\n const current = next.get(assignment.userId) ?? []\n if (!current.includes(label)) {\n current.push(label)\n next.set(assignment.userId, current)\n }\n })\n return next\n }, [existingAssignments, roleTypeLabelMap, selectedRoleType])\n\n const selectedUserConflict = React.useMemo(() => {\n if (!selectedUser) return null\n return conflictsByUserId.get(selectedUser.id) ?? null\n }, [conflictsByUserId, selectedUser])\n\n const teamFilters = React.useMemo<TeamFilter[]>(() => {\n const counts = new Map<string, number>()\n users.forEach((user) => {\n const key =\n user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')\n counts.set(key, (counts.get(key) ?? 0) + 1)\n })\n return [\n { id: 'all', label: t('customers.roles.dialog.team.all', 'All'), count: users.length },\n ...Array.from(counts.entries()).map(([label, count]) => ({ id: label, label, count })),\n ]\n }, [t, users])\n\n const filteredUsers = React.useMemo(() => {\n if (activeTeam === 'all') return users\n return users.filter(\n (user) =>\n (user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')) ===\n activeTeam,\n )\n }, [activeTeam, t, users])\n\n const visibleCountLabel = React.useMemo(() => {\n if (totalUsers <= 0) return null\n return t(\n 'customers.roles.dialog.visibleCount',\n 'Showing {{shown}} of {{total}} team members',\n {\n shown: String(users.length),\n total: String(totalUsers),\n },\n )\n }, [t, totalUsers, users.length])\n\n const canLoadMore = users.length < totalUsers\n\n const handleAssign = React.useCallback(async () => {\n if (!selectedRoleType || !selectedUser) return\n setSaving(true)\n try {\n await onAssign(selectedRoleType, selectedUser.id)\n onClose()\n } finally {\n setSaving(false)\n }\n }, [onAssign, onClose, selectedRoleType, selectedUser])\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n if (step === 3 && !saving && selectedUser && selectedRoleType) {\n handleAssign()\n } else if (step === 1 && selectedRoleType && availableRoleTypes.length) {\n setStep(2)\n } else if (step === 2 && selectedUser) {\n setStep(3)\n }\n }\n },\n [availableRoleTypes.length, handleAssign, saving, selectedRoleType, selectedUser, step],\n )\n\n const previewCard =\n selectedUser && selectedRole ? (\n <div className=\"rounded-lg border border-border/70 bg-muted/30 px-4 py-4\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.preview', 'Assignment preview')}\n </p>\n <div className=\"mt-3 flex items-center gap-3\">\n <div className=\"flex size-12 items-center justify-center rounded-full bg-background text-sm font-semibold text-foreground\">\n {getInitials(selectedUser.displayName)}\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-sm font-semibold text-foreground\">\n <span>{selectedUser.displayName}</span>\n <span className=\"text-muted-foreground\">\u2192</span>\n <span>{selectedRole.label}</span>\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {selectedUser.email ? <span>{selectedUser.email}</span> : null}\n {selectedUser.teamName ? <span>{selectedUser.teamName}</span> : null}\n </div>\n {selectedUserConflict?.length ? (\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <Badge\n variant=\"outline\"\n className=\"rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text\"\n >\n {t('customers.roles.dialog.conflict', 'Conflict: {{roles}}', {\n roles: selectedUserConflict.join(', '),\n })}\n </Badge>\n </div>\n ) : null}\n </div>\n </div>\n </div>\n ) : null\n\n return (\n <Dialog\n open={open}\n onOpenChange={(nextOpen) => {\n if (!nextOpen) onClose()\n }}\n >\n <DialogContent\n className=\"min-h-0 max-h-[min(90vh,760px)] overflow-hidden p-0 sm:max-w-[580px]\"\n onKeyDown={handleKeyDown}\n >\n <DialogHeader className=\"border-b border-border/70 px-6 py-5\">\n <DialogTitle className=\"text-2xl font-semibold leading-none\">\n {t('customers.roles.dialog.title', 'Assign role')}\n </DialogTitle>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {t('customers.roles.dialog.subtitle', 'Multi-role assignment for {{name}}', {\n name: entityName,\n })}\n </p>\n </DialogHeader>\n\n <div className=\"border-b border-border/70 px-6 py-4\">\n <div className=\"flex items-center justify-center gap-3 text-xs\">\n <StepBadge\n step={1}\n currentStep={step}\n label={t('customers.roles.dialog.step1', 'Role type')}\n />\n <div className=\"h-px w-10 bg-border\" />\n <StepBadge\n step={2}\n currentStep={step}\n label={t('customers.roles.dialog.step2', 'Select person')}\n />\n <div className=\"h-px w-10 bg-border\" />\n <StepBadge\n step={3}\n currentStep={step}\n label={t('customers.roles.dialog.step3', 'Confirm')}\n />\n </div>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n <div className=\"space-y-5 px-6 py-5\">\n {step === 1 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <p\n id=\"assign-role-dialog-type-label\"\n className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\"\n >\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <select\n aria-labelledby=\"assign-role-dialog-type-label\"\n value={selectedRoleType}\n onChange={(event) => setSelectedRoleType(event.target.value)}\n className=\"h-10 w-full rounded-md border border-border bg-background px-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring/30\"\n >\n <option value=\"\">\n {t('customers.roles.selectRoleType', 'Select role type...')}\n </option>\n {availableRoleTypes.map((roleType) => (\n <option key={roleType.id} value={roleType.value}>\n {roleType.label}\n </option>\n ))}\n </select>\n {selectedRole ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n ) : null}\n </div>\n </div>\n\n {!availableRoleTypes.length ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-6 text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.noAvailableRoles',\n 'All available role types are already assigned.',\n )}\n </div>\n ) : null}\n </div>\n ) : null}\n\n {step === 2 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-1 flex items-center gap-2\">\n <span className=\"text-lg font-semibold text-foreground\">\n {selectedRole?.label ?? selectedRoleType}\n </span>\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n </div>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setStep(1)}\n >\n {t('customers.roles.dialog.change', 'Change')}\n </Button>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.teamLabel', 'Select a team member')}\n </p>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n placeholder={t(\n 'customers.roles.dialog.searchPlaceholder',\n 'Search by name, e-mail or team...',\n )}\n className=\"h-10 rounded-md border-border/80 pl-9 shadow-none\"\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {teamFilters.map((teamFilter) => {\n const isActive = activeTeam === teamFilter.id\n return (\n <Button\n key={teamFilter.id}\n type=\"button\"\n variant={isActive ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setActiveTeam(teamFilter.id)}\n className=\"h-7 rounded-md px-2.5 text-xs\"\n >\n {teamFilter.label}\n <span className=\"rounded-full bg-background/80 px-1 text-xs text-muted-foreground\">\n {teamFilter.count}\n </span>\n </Button>\n )\n })}\n </div>\n\n <div className=\"space-y-2\">\n {visibleCountLabel ? (\n <p className=\"text-xs text-muted-foreground\">{visibleCountLabel}</p>\n ) : null}\n {loading ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t('customers.roles.loading', 'Loading...')}\n </div>\n ) : loadError ? (\n <div className=\"rounded-lg border border-dashed border-status-error-border bg-status-error-bg/70 px-4 py-8 text-center text-sm text-status-error-text\">\n {loadError}\n </div>\n ) : filteredUsers.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.noResults',\n 'No matching team members found.',\n )}\n </div>\n ) : (\n filteredUsers.map((user) => {\n const isSelected = selectedUser?.id === user.id\n const userConflicts = conflictsByUserId.get(user.id) ?? []\n return (\n <Button\n key={user.id}\n type=\"button\"\n variant=\"ghost\"\n onClick={() => setSelectedUser(user)}\n className={`h-auto flex w-full items-center gap-3 rounded-lg border px-4 py-3 text-left transition-colors ${\n isSelected\n ? 'border-foreground bg-background shadow-sm'\n : 'border-border/70 bg-background hover:bg-accent/40'\n }`}\n >\n <div className=\"flex size-11 shrink-0 items-center justify-center rounded-full bg-muted text-sm font-semibold text-foreground\">\n {getInitials(user.displayName)}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">\n {user.displayName}\n </span>\n {user.teamName ? (\n <Badge\n variant=\"muted\"\n className=\"rounded-md px-2 py-0.5 text-xs font-medium\"\n >\n {user.teamName}\n </Badge>\n ) : null}\n {userConflicts.length ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text\"\n >\n {t('customers.roles.dialog.conflict', 'Conflict: {{roles}}', {\n roles: userConflicts.join(', '),\n })}\n </Badge>\n ) : null}\n </div>\n {user.email ? (\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {user.email}\n </div>\n ) : null}\n </div>\n <span\n className={`flex size-6 shrink-0 items-center justify-center rounded-full border ${\n isSelected\n ? 'border-foreground bg-foreground text-background'\n : 'border-border/80 bg-background text-transparent'\n }`}\n >\n <Check className=\"size-3.5\" />\n </span>\n </Button>\n )\n })\n )}\n {canLoadMore && !loadError ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"w-full\"\n >\n {loadingMore\n ? t('customers.roles.dialog.loadingMore', 'Loading more...')\n : t('customers.roles.dialog.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n\n {previewCard}\n </div>\n ) : null}\n\n {step === 3 ? (\n <div className=\"space-y-4\">\n {previewCard}\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.constraint',\n 'One person per role. The assignment can be changed at any time.',\n )}\n </p>\n </div>\n ) : null}\n </div>\n </div>\n\n <DialogFooter className=\"shrink-0 border-t border-border/70 px-6 py-4 sm:justify-between\">\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'customers.roles.dialog.footerNote',\n 'One person per role \u00B7 can be changed at any time',\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {t('customers.roles.cancelAdd', 'Cancel')}\n </Button>\n {step === 1 ? (\n <Button\n type=\"button\"\n onClick={() => setStep(2)}\n disabled={!selectedRoleType || !availableRoleTypes.length}\n >\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 2 ? (\n <Button type=\"button\" onClick={() => setStep(3)} disabled={!selectedUser}>\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 3 ? (\n <Button\n type=\"button\"\n onClick={handleAssign}\n disabled={saving || !selectedUser || !selectedRoleType}\n >\n {saving\n ? t('customers.roles.assigning', 'Assigning...')\n : t('customers.roles.dialog.assign', 'Assign role')}\n </Button>\n ) : null}\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction StepBadge({\n step,\n currentStep,\n label,\n}: {\n step: StepId\n currentStep: StepId\n label: string\n}) {\n const isComplete = currentStep > step\n const isCurrent = currentStep === step\n\n return (\n <div className=\"flex items-center gap-2\">\n <span\n className={`flex size-5 items-center justify-center rounded-full border text-xs font-semibold ${\n isComplete || isCurrent\n ? 'border-foreground bg-foreground text-background'\n : 'border-border bg-background text-muted-foreground'\n }`}\n >\n {isComplete ? <Check className=\"size-3\" /> : step}\n </span>\n <span className={isCurrent ? 'font-semibold text-foreground' : 'text-muted-foreground'}>\n {label}\n </span>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Search, Check, Settings2 } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport type { DictionaryEntryOption } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'\nimport type { RoleAssignment } from './RoleAssignmentRow'\nimport { fetchAssignableStaffMembersPage } from './assignableStaff'\nimport { getInitials } from './utils'\n\nconst MANAGE_ROLE_TYPES_HREF = '/backend/config/customers'\n\ntype StaffMember = {\n id: string\n displayName: string\n email: string | null\n teamName: string | null\n}\n\ninterface AssignRoleDialogProps {\n open: boolean\n onClose: () => void\n onAssign: (roleType: string, userId: string) => Promise<void>\n roleTypes: DictionaryEntryOption[]\n entityName: string\n existingRoleTypes?: Set<string>\n existingAssignments?: RoleAssignment[]\n initialRoleType?: string | null\n canManageRoleTypes?: boolean\n}\n\ntype StepId = 1 | 2 | 3\n\ntype TeamFilter = {\n id: string\n label: string\n count: number\n}\n\nconst ASSIGNABLE_STAFF_PAGE_SIZE = 24\n\nexport function AssignRoleDialog({\n open,\n onClose,\n onAssign,\n roleTypes,\n entityName,\n existingRoleTypes,\n existingAssignments = [],\n initialRoleType = null,\n canManageRoleTypes = false,\n}: AssignRoleDialogProps) {\n const t = useT()\n const [step, setStep] = React.useState<StepId>(1)\n const [selectedRoleType, setSelectedRoleType] = React.useState('')\n const [selectedUser, setSelectedUser] = React.useState<StaffMember | null>(null)\n const [searchQuery, setSearchQuery] = React.useState('')\n const [users, setUsers] = React.useState<StaffMember[]>([])\n const [loading, setLoading] = React.useState(false)\n const [loadingMore, setLoadingMore] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [activeTeam, setActiveTeam] = React.useState('all')\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [totalUsers, setTotalUsers] = React.useState(0)\n const [currentPage, setCurrentPage] = React.useState(1)\n const deferredSearchQuery = React.useDeferredValue(searchQuery)\n const requestSequenceRef = React.useRef(0)\n\n const availableRoleTypes = React.useMemo(\n () => roleTypes.filter((roleType) => !existingRoleTypes?.has(roleType.value)),\n [existingRoleTypes, roleTypes],\n )\n\n React.useEffect(() => {\n if (!open) {\n setStep(1)\n setSelectedRoleType('')\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n return\n }\n const resolvedInitialRoleType =\n typeof initialRoleType === 'string' && initialRoleType.trim().length > 0\n ? initialRoleType.trim()\n : ''\n setStep(resolvedInitialRoleType ? 2 : 1)\n setSelectedRoleType(resolvedInitialRoleType)\n setSelectedUser(null)\n setSearchQuery('')\n setUsers([])\n setActiveTeam('all')\n setLoadError(null)\n setTotalUsers(0)\n setCurrentPage(1)\n requestSequenceRef.current = 0\n }, [initialRoleType, open])\n\n const searchUsers = React.useCallback(\n async ({\n query,\n page,\n append,\n }: {\n query: string\n page: number\n append: boolean\n }) => {\n const requestId = append ? requestSequenceRef.current : requestSequenceRef.current + 1\n requestSequenceRef.current = requestId\n\n if (append) {\n setLoadingMore(true)\n } else {\n setLoading(true)\n }\n\n try {\n const result = await fetchAssignableStaffMembersPage(query, {\n page,\n pageSize: ASSIGNABLE_STAFF_PAGE_SIZE,\n })\n if (requestSequenceRef.current !== requestId) return\n\n const nextUsers = result.items.map((member) => ({\n id: member.userId,\n displayName: member.displayName,\n email: member.email,\n teamName: member.teamName,\n }))\n\n setUsers((current) => {\n if (!append) return nextUsers\n const merged = new Map(current.map((user) => [user.id, user]))\n nextUsers.forEach((user) => merged.set(user.id, user))\n return Array.from(merged.values())\n })\n setTotalUsers(result.total)\n setCurrentPage(result.page)\n setLoadError(null)\n } catch {\n if (requestSequenceRef.current !== requestId) return\n if (!append) {\n setUsers([])\n setTotalUsers(0)\n setCurrentPage(1)\n }\n setLoadError(\n t(\n 'customers.assignableStaff.loadError',\n 'Unable to load team members. Check your permissions and try again.',\n ),\n )\n } finally {\n if (requestSequenceRef.current !== requestId) return\n if (append) {\n setLoadingMore(false)\n } else {\n setLoading(false)\n }\n }\n },\n [t],\n )\n\n React.useEffect(() => {\n if (step >= 2) {\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({ query: deferredSearchQuery, page: 1, append: false }).catch(() => {})\n }\n }, [deferredSearchQuery, searchUsers, step])\n\n const handleLoadMore = React.useCallback(() => {\n if (loading || loadingMore || users.length >= totalUsers) return\n // fire-and-forget: search results populate async; errors shown in list UI\n searchUsers({\n query: deferredSearchQuery,\n page: currentPage + 1,\n append: true,\n }).catch(() => {})\n }, [currentPage, deferredSearchQuery, loading, loadingMore, searchUsers, totalUsers, users.length])\n\n const selectedRole = React.useMemo(\n () => roleTypes.find((roleType) => roleType.value === selectedRoleType) ?? null,\n [roleTypes, selectedRoleType],\n )\n\n const roleTypeLabelMap = React.useMemo(\n () => new Map(roleTypes.map((roleType) => [roleType.value, roleType.label])),\n [roleTypes],\n )\n\n const conflictsByUserId = React.useMemo(() => {\n const next = new Map<string, string[]>()\n existingAssignments.forEach((assignment) => {\n if (!assignment.userId) return\n if (assignment.roleType === selectedRoleType) return\n const label = roleTypeLabelMap.get(assignment.roleType) ?? assignment.roleType\n const current = next.get(assignment.userId) ?? []\n if (!current.includes(label)) {\n current.push(label)\n next.set(assignment.userId, current)\n }\n })\n return next\n }, [existingAssignments, roleTypeLabelMap, selectedRoleType])\n\n const selectedUserConflict = React.useMemo(() => {\n if (!selectedUser) return null\n return conflictsByUserId.get(selectedUser.id) ?? null\n }, [conflictsByUserId, selectedUser])\n\n const teamFilters = React.useMemo<TeamFilter[]>(() => {\n const counts = new Map<string, number>()\n users.forEach((user) => {\n const key =\n user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')\n counts.set(key, (counts.get(key) ?? 0) + 1)\n })\n return [\n { id: 'all', label: t('customers.roles.dialog.team.all', 'All'), count: users.length },\n ...Array.from(counts.entries()).map(([label, count]) => ({ id: label, label, count })),\n ]\n }, [t, users])\n\n const filteredUsers = React.useMemo(() => {\n if (activeTeam === 'all') return users\n return users.filter(\n (user) =>\n (user.teamName?.trim() || t('customers.roles.dialog.team.unassigned', 'No team')) ===\n activeTeam,\n )\n }, [activeTeam, t, users])\n\n const visibleCountLabel = React.useMemo(() => {\n if (totalUsers <= 0) return null\n return t(\n 'customers.roles.dialog.visibleCount',\n 'Showing {{shown}} of {{total}} team members',\n {\n shown: String(users.length),\n total: String(totalUsers),\n },\n )\n }, [t, totalUsers, users.length])\n\n const canLoadMore = users.length < totalUsers\n\n const handleAssign = React.useCallback(async () => {\n if (!selectedRoleType || !selectedUser) return\n setSaving(true)\n try {\n await onAssign(selectedRoleType, selectedUser.id)\n onClose()\n } finally {\n setSaving(false)\n }\n }, [onAssign, onClose, selectedRoleType, selectedUser])\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n if (step === 3 && !saving && selectedUser && selectedRoleType) {\n handleAssign()\n } else if (step === 1 && selectedRoleType && availableRoleTypes.length) {\n setStep(2)\n } else if (step === 2 && selectedUser) {\n setStep(3)\n }\n }\n },\n [availableRoleTypes.length, handleAssign, saving, selectedRoleType, selectedUser, step],\n )\n\n const previewCard =\n selectedUser && selectedRole ? (\n <div className=\"rounded-lg border border-border/70 bg-muted/30 px-4 py-4\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.preview', 'Assignment preview')}\n </p>\n <div className=\"mt-3 flex items-center gap-3\">\n <div className=\"flex size-12 items-center justify-center rounded-full bg-background text-sm font-semibold text-foreground\">\n {getInitials(selectedUser.displayName)}\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2 text-sm font-semibold text-foreground\">\n <span>{selectedUser.displayName}</span>\n <span className=\"text-muted-foreground\">\u2192</span>\n <span>{selectedRole.label}</span>\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {selectedUser.email ? <span>{selectedUser.email}</span> : null}\n {selectedUser.teamName ? <span>{selectedUser.teamName}</span> : null}\n </div>\n {selectedUserConflict?.length ? (\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <Badge\n variant=\"outline\"\n className=\"rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text\"\n >\n {t('customers.roles.dialog.conflict', 'Conflict: {{roles}}', {\n roles: selectedUserConflict.join(', '),\n })}\n </Badge>\n </div>\n ) : null}\n </div>\n </div>\n </div>\n ) : null\n\n return (\n <Dialog\n open={open}\n onOpenChange={(nextOpen) => {\n if (!nextOpen) onClose()\n }}\n >\n <DialogContent\n className=\"min-h-0 max-h-[min(90vh,760px)] overflow-hidden p-0 sm:max-w-[580px]\"\n onKeyDown={handleKeyDown}\n >\n <DialogHeader className=\"border-b border-border/70 px-6 py-5\">\n <DialogTitle className=\"text-2xl font-semibold leading-none\">\n {t('customers.roles.dialog.title', 'Assign role')}\n </DialogTitle>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {t('customers.roles.dialog.subtitle', 'Multi-role assignment for {{name}}', {\n name: entityName,\n })}\n </p>\n </DialogHeader>\n\n <div className=\"border-b border-border/70 px-6 py-4\">\n <div className=\"flex items-center justify-center gap-3 text-xs\">\n <StepBadge\n step={1}\n currentStep={step}\n label={t('customers.roles.dialog.step1', 'Role type')}\n />\n <div className=\"h-px w-10 bg-border\" />\n <StepBadge\n step={2}\n currentStep={step}\n label={t('customers.roles.dialog.step2', 'Select person')}\n />\n <div className=\"h-px w-10 bg-border\" />\n <StepBadge\n step={3}\n currentStep={step}\n label={t('customers.roles.dialog.step3', 'Confirm')}\n />\n </div>\n </div>\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n <div className=\"space-y-5 px-6 py-5\">\n {step === 1 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <p\n id=\"assign-role-dialog-type-label\"\n className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\"\n >\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <select\n aria-labelledby=\"assign-role-dialog-type-label\"\n value={selectedRoleType}\n onChange={(event) => setSelectedRoleType(event.target.value)}\n className=\"h-10 w-full rounded-md border border-border bg-background px-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring/30\"\n >\n <option value=\"\">\n {t('customers.roles.selectRoleType', 'Select role type...')}\n </option>\n {availableRoleTypes.map((roleType) => (\n <option key={roleType.id} value={roleType.value}>\n {roleType.label}\n </option>\n ))}\n </select>\n {selectedRole ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n ) : null}\n </div>\n {canManageRoleTypes ? (\n <Link\n href={MANAGE_ROLE_TYPES_HREF}\n className=\"mt-3 inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:underline\"\n data-testid=\"assign-role-dialog-manage-role-types\"\n >\n <Settings2 className=\"size-3.5\" aria-hidden=\"true\" />\n {t('customers.roles.dialog.manageRoleTypes', 'Manage role types')}\n </Link>\n ) : null}\n </div>\n\n {!availableRoleTypes.length ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-6 text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.noAvailableRoles',\n 'All available role types are already assigned.',\n )}\n </div>\n ) : null}\n </div>\n ) : null}\n\n {step === 2 ? (\n <div className=\"space-y-4\">\n <div className=\"rounded-lg bg-muted/30 px-4 py-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.roleTypeLabel', 'Role type')}\n </p>\n <div className=\"mt-1 flex items-center gap-2\">\n <span className=\"text-lg font-semibold text-foreground\">\n {selectedRole?.label ?? selectedRoleType}\n </span>\n <Badge\n variant=\"outline\"\n className=\"rounded-md px-2 py-1 text-xs\"\n >\n {t('customers.roles.dialog.sourceBadge.dictionary', 'Dictionary')}\n </Badge>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {canManageRoleTypes ? (\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link\n href={MANAGE_ROLE_TYPES_HREF}\n data-testid=\"assign-role-dialog-manage-role-types-step2\"\n >\n <Settings2 className=\"mr-1 size-3.5\" aria-hidden=\"true\" />\n {t('customers.roles.dialog.manageRoleTypes', 'Manage role types')}\n </Link>\n </Button>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setStep(1)}\n >\n {t('customers.roles.dialog.change', 'Change')}\n </Button>\n </div>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <p className=\"text-overline font-semibold uppercase tracking-[0.12em] text-muted-foreground\">\n {t('customers.roles.dialog.teamLabel', 'Select a team member')}\n </p>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n placeholder={t(\n 'customers.roles.dialog.searchPlaceholder',\n 'Search by name, e-mail or team...',\n )}\n className=\"h-10 rounded-md border-border/80 pl-9 shadow-none\"\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {teamFilters.map((teamFilter) => {\n const isActive = activeTeam === teamFilter.id\n return (\n <Button\n key={teamFilter.id}\n type=\"button\"\n variant={isActive ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setActiveTeam(teamFilter.id)}\n className=\"h-7 rounded-md px-2.5 text-xs\"\n >\n {teamFilter.label}\n <span className=\"rounded-full bg-background/80 px-1 text-xs text-muted-foreground\">\n {teamFilter.count}\n </span>\n </Button>\n )\n })}\n </div>\n\n <div className=\"space-y-2\">\n {visibleCountLabel ? (\n <p className=\"text-xs text-muted-foreground\">{visibleCountLabel}</p>\n ) : null}\n {loading ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t('customers.roles.loading', 'Loading...')}\n </div>\n ) : loadError ? (\n <div className=\"rounded-lg border border-dashed border-status-error-border bg-status-error-bg/70 px-4 py-8 text-center text-sm text-status-error-text\">\n {loadError}\n </div>\n ) : filteredUsers.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/80 px-4 py-8 text-center text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.noResults',\n 'No matching team members found.',\n )}\n </div>\n ) : (\n filteredUsers.map((user) => {\n const isSelected = selectedUser?.id === user.id\n const userConflicts = conflictsByUserId.get(user.id) ?? []\n return (\n <Button\n key={user.id}\n type=\"button\"\n variant=\"ghost\"\n onClick={() => setSelectedUser(user)}\n className={`h-auto flex w-full items-center gap-3 rounded-lg border px-4 py-3 text-left transition-colors ${\n isSelected\n ? 'border-foreground bg-background shadow-sm'\n : 'border-border/70 bg-background hover:bg-accent/40'\n }`}\n >\n <div className=\"flex size-11 shrink-0 items-center justify-center rounded-full bg-muted text-sm font-semibold text-foreground\">\n {getInitials(user.displayName)}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">\n {user.displayName}\n </span>\n {user.teamName ? (\n <Badge\n variant=\"muted\"\n className=\"rounded-md px-2 py-0.5 text-xs font-medium\"\n >\n {user.teamName}\n </Badge>\n ) : null}\n {userConflicts.length ? (\n <Badge\n variant=\"outline\"\n className=\"rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-semibold text-status-error-text\"\n >\n {t('customers.roles.dialog.conflict', 'Conflict: {{roles}}', {\n roles: userConflicts.join(', '),\n })}\n </Badge>\n ) : null}\n </div>\n {user.email ? (\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {user.email}\n </div>\n ) : null}\n </div>\n <span\n className={`flex size-6 shrink-0 items-center justify-center rounded-full border ${\n isSelected\n ? 'border-foreground bg-foreground text-background'\n : 'border-border/80 bg-background text-transparent'\n }`}\n >\n <Check className=\"size-3.5\" />\n </span>\n </Button>\n )\n })\n )}\n {canLoadMore && !loadError ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"w-full\"\n >\n {loadingMore\n ? t('customers.roles.dialog.loadingMore', 'Loading more...')\n : t('customers.roles.dialog.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n\n {previewCard}\n </div>\n ) : null}\n\n {step === 3 ? (\n <div className=\"space-y-4\">\n {previewCard}\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.roles.dialog.constraint',\n 'One person per role. The assignment can be changed at any time.',\n )}\n </p>\n </div>\n ) : null}\n </div>\n </div>\n\n <DialogFooter className=\"shrink-0 border-t border-border/70 px-6 py-4 sm:justify-between\">\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'customers.roles.dialog.footerNote',\n 'One person per role \u00B7 can be changed at any time',\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {t('customers.roles.cancelAdd', 'Cancel')}\n </Button>\n {step === 1 ? (\n <Button\n type=\"button\"\n onClick={() => setStep(2)}\n disabled={!selectedRoleType || !availableRoleTypes.length}\n >\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 2 ? (\n <Button type=\"button\" onClick={() => setStep(3)} disabled={!selectedUser}>\n {t('customers.roles.dialog.next', 'Next')}\n </Button>\n ) : null}\n {step === 3 ? (\n <Button\n type=\"button\"\n onClick={handleAssign}\n disabled={saving || !selectedUser || !selectedRoleType}\n >\n {saving\n ? t('customers.roles.assigning', 'Assigning...')\n : t('customers.roles.dialog.assign', 'Assign role')}\n </Button>\n ) : null}\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\nfunction StepBadge({\n step,\n currentStep,\n label,\n}: {\n step: StepId\n currentStep: StepId\n label: string\n}) {\n const isComplete = currentStep > step\n const isCurrent = currentStep === step\n\n return (\n <div className=\"flex items-center gap-2\">\n <span\n className={`flex size-5 items-center justify-center rounded-full border text-xs font-semibold ${\n isComplete || isCurrent\n ? 'border-foreground bg-foreground text-background'\n : 'border-border bg-background text-muted-foreground'\n }`}\n >\n {isComplete ? <Check className=\"size-3\" /> : step}\n </span>\n <span className={isCurrent ? 'font-semibold text-foreground' : 'text-muted-foreground'}>\n {label}\n </span>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,10 +7,13 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
7
7
  import { Button } from "@open-mercato/ui/primitives/button";
8
8
  import { IconButton } from "@open-mercato/ui/primitives/icon-button";
9
9
  import { Badge } from "@open-mercato/ui/primitives/badge";
10
+ import { SendObjectMessageDialog } from "@open-mercato/ui/backend/messages";
10
11
  import { CompanyTagsDialog } from "./CompanyTagsDialog.js";
12
+ import { ObjectHistoryButton } from "./ObjectHistoryButton.js";
11
13
  import { invalidateCustomerDictionary, useCustomerDictionary } from "./hooks/useCustomerDictionary.js";
12
14
  import { renderDictionaryIcon } from "../../../dictionaries/components/dictionaryAppearance.js";
13
15
  import { formatFallbackLabel } from "./utils.js";
16
+ const HEADER_ICON_BUTTON_CLASS = "size-8 rounded-md";
14
17
  function CompanyDictionaryBadge({ value, map }) {
15
18
  const entry = map?.[value];
16
19
  const color = entry?.color ?? null;
@@ -131,6 +134,33 @@ function CompanyDetailHeader({
131
134
  ] })
132
135
  ] }),
133
136
  /* @__PURE__ */ jsx("div", { className: "flex w-full shrink-0 flex-col items-start gap-3 sm:w-auto sm:items-end", children: /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-wrap items-center justify-start gap-2 sm:w-auto sm:justify-end", children: [
137
+ /* @__PURE__ */ jsx(
138
+ SendObjectMessageDialog,
139
+ {
140
+ object: {
141
+ entityModule: "customers",
142
+ entityType: "company",
143
+ entityId: company.id,
144
+ previewData: {
145
+ title: displayName,
146
+ subtitle: company.primaryEmail ?? profile?.websiteUrl ?? void 0
147
+ }
148
+ },
149
+ viewHref: `/backend/customers/companies-v2/${company.id}`,
150
+ buttonVariant: "outline",
151
+ buttonSize: "icon",
152
+ buttonClassName: HEADER_ICON_BUTTON_CLASS,
153
+ buttonLabel: t("customers.companies.detail.actions.sendMessage", "Send message")
154
+ }
155
+ ),
156
+ /* @__PURE__ */ jsx(
157
+ ObjectHistoryButton,
158
+ {
159
+ resourceKind: "customers.company",
160
+ resourceId: company.id,
161
+ organizationId: company.organizationId ?? void 0
162
+ }
163
+ ),
134
164
  /* @__PURE__ */ jsx(
135
165
  IconButton,
136
166
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/CompanyDetailHeader.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Phone, Mail, Trash2, Building2, Globe, Pencil, MapPin } from 'lucide-react'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { CompanyTagsDialog } from './CompanyTagsDialog'\nimport { invalidateCustomerDictionary, useCustomerDictionary } from './hooks/useCustomerDictionary'\nimport { renderDictionaryIcon } from '../../../dictionaries/components/dictionaryAppearance'\nimport type { TagSummary } from './types'\nimport type { TagsSectionController } from '@open-mercato/ui/backend/detail'\nimport type { CompanyOverview } from '../formConfig'\nimport type { CustomerDictionaryMap } from '@open-mercato/core/modules/customers/lib/dictionaries'\nimport { formatFallbackLabel } from './utils'\n\ntype CompanyDetailHeaderProps = {\n data: CompanyOverview\n onTagsChange: (tags: TagSummary[]) => void\n tagsSectionControllerRef: React.RefObject<TagsSectionController | null>\n onSave: () => void\n onDelete: () => Promise<void>\n isDirty: boolean\n isSaving: boolean\n onFocusField?: (fieldName: string) => void\n onDataReload?: () => void\n}\n\nfunction CompanyDictionaryBadge({ value, map }: { value: string; map: CustomerDictionaryMap | undefined }) {\n const entry = map?.[value]\n const color = entry?.color ?? null\n const icon = entry?.icon ?? null\n const label = entry?.label ?? formatFallbackLabel(value)\n const colorStyle: React.CSSProperties | undefined = color\n ? { color, borderColor: color, backgroundColor: `${color}1A` }\n : undefined\n return (\n <Badge variant=\"outline\" className=\"rounded-sm gap-1.5 text-xs font-medium\" style={colorStyle}>\n {icon ? renderDictionaryIcon(icon, 'size-2.5') : null}\n {label}\n </Badge>\n )\n}\n\nexport function CompanyDetailHeader({\n data,\n onTagsChange,\n tagsSectionControllerRef,\n onSave,\n onDelete,\n isDirty,\n isSaving,\n onFocusField,\n onDataReload,\n}: CompanyDetailHeaderProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const [manageTagsOpen, setManageTagsOpen] = React.useState(false)\n const company = data.company\n const profile = data.profile\n const displayName = company.displayName || t('customers.companies.detail.untitled', 'Untitled')\n const visibleCustomTags = React.useMemo(\n () => (data.tags?.filter((tag) => !['status', 'lifecycle_stage', 'source'].includes(tag.id)).slice(0, 6) ?? []),\n [data.tags],\n )\n const hiddenCustomTagsCount = Math.max(\n 0,\n (data.tags?.filter((tag) => !['status', 'lifecycle_stage', 'source'].includes(tag.id)).length ?? 0) - visibleCustomTags.length,\n )\n\n const industryLabel = profile?.industry ?? null\n const sizeLabel = profile?.sizeBucket ?? null\n const subtitle = [industryLabel, sizeLabel ? t('customers.companies.detail.header.employees', '{count} employees', { count: sizeLabel }) : null].filter(Boolean).join(' \\u00b7 ')\n\n // Primary address for header\n const primaryAddress = React.useMemo(() => {\n const addresses = (data as Record<string, unknown>).addresses as Array<{ isPrimary?: boolean; city?: string; region?: string; postalCode?: string }> | undefined\n if (!addresses || !Array.isArray(addresses)) return null\n return addresses.find((a) => a.isPrimary) ?? addresses[0] ?? null\n }, [data])\n\n const locationText = React.useMemo(() => {\n if (!primaryAddress) return null\n return [primaryAddress.city, primaryAddress.region, primaryAddress.postalCode].filter(Boolean).join(', ')\n }, [primaryAddress])\n\n // Fetch dictionary maps for colored badge rendering\n const companyOrgId = company.organizationId ?? null\n const { data: statusDict } = useCustomerDictionary('statuses', 0, companyOrgId)\n const { data: lifecycleDict } = useCustomerDictionary('lifecycle-stages', 0, companyOrgId)\n const { data: sourceDict } = useCustomerDictionary('sources', 0, companyOrgId)\n const { data: temperatureDict } = useCustomerDictionary('temperature', 0, companyOrgId)\n const { data: renewalQuarterDict } = useCustomerDictionary('renewal-quarters', 0, companyOrgId)\n\n return (\n <div className=\"rounded-lg border bg-card\">\n {/* Top row: avatar + company info + account manager + actions */}\n <div className=\"flex flex-col gap-4 px-6 pt-6 pb-3 sm:flex-row sm:items-start sm:gap-5\">\n {/* Avatar */}\n <div className=\"flex size-18 shrink-0 items-center justify-center rounded-full bg-muted\">\n <Building2 className=\"size-7 text-muted-foreground\" />\n </div>\n\n {/* Company info */}\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"truncate text-2xl font-bold text-foreground\">{displayName}</h1>\n {subtitle && (\n <p className=\"mt-0.5 text-sm text-muted-foreground\">{subtitle}</p>\n )}\n\n {/* Contact row */}\n <div className=\"mt-1.5 flex flex-wrap items-center gap-x-5 gap-y-1 text-sm text-muted-foreground\">\n {company.primaryPhone && (\n <span className=\"inline-flex items-center gap-1.5\">\n <Phone className=\"size-3.5\" />\n <a href={`tel:${company.primaryPhone}`} className=\"hover:text-foreground\">{company.primaryPhone}</a>\n </span>\n )}\n {company.primaryEmail && (\n <span className=\"inline-flex items-center gap-1.5\">\n <Mail className=\"size-3.5\" />\n <a href={`mailto:${company.primaryEmail}`} className=\"hover:text-foreground\">{company.primaryEmail}</a>\n </span>\n )}\n {profile?.websiteUrl && (\n <a href={profile.websiteUrl.startsWith('http') ? profile.websiteUrl : `https://${profile.websiteUrl}`} target=\"_blank\" rel=\"noreferrer\" className=\"inline-flex items-center gap-1.5 hover:text-foreground\">\n <Globe className=\"size-3.5\" />\n {profile.websiteUrl}\n </a>\n )}\n {locationText && (\n <span className=\"inline-flex items-center gap-1.5\">\n <MapPin className=\"size-3.5\" />\n {locationText}\n </span>\n )}\n </div>\n\n {/* Status badges + temperature + renewal quarter + inline tags */}\n <div className=\"mt-2.5 flex flex-wrap items-center gap-1.5\">\n {company.status && (\n <CompanyDictionaryBadge value={company.status} map={statusDict?.map} />\n )}\n {company.lifecycleStage && (\n <CompanyDictionaryBadge value={company.lifecycleStage} map={lifecycleDict?.map} />\n )}\n {company.source && (\n <CompanyDictionaryBadge value={company.source} map={sourceDict?.map} />\n )}\n {company.temperature && (\n <CompanyDictionaryBadge value={company.temperature} map={temperatureDict?.map} />\n )}\n {company.renewalQuarter && (\n <CompanyDictionaryBadge value={company.renewalQuarter} map={renewalQuarterDict?.map} />\n )}\n {visibleCustomTags.map((tag) => {\n const colorStyle: React.CSSProperties | undefined = tag.color\n ? { color: tag.color, borderColor: tag.color, backgroundColor: `${tag.color}1A` }\n : undefined\n return (\n <Badge\n key={tag.id}\n variant=\"outline\"\n className=\"rounded-sm gap-1.5 text-xs font-medium\"\n style={colorStyle}\n >\n {tag.label}\n </Badge>\n )\n })}\n {hiddenCustomTagsCount > 0 ? (\n <Badge variant=\"outline\" className=\"rounded-sm gap-1.5 text-xs font-medium\">\n +{hiddenCustomTagsCount} {t('customers.companies.detail.header.more', 'more')}\n </Badge>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto rounded-sm px-2 py-1 text-xs font-medium text-muted-foreground hover:text-foreground\"\n onClick={() => setManageTagsOpen(true)}\n >\n <Pencil className=\"mr-1 size-3\" />\n {t('customers.companies.detail.actions.manageTags', 'Edit tags')}\n </Button>\n </div>\n </div>\n\n {/* Right side: actions */}\n <div className=\"flex w-full shrink-0 flex-col items-start gap-3 sm:w-auto sm:items-end\">\n <div className=\"flex w-full flex-wrap items-center justify-start gap-2 sm:w-auto sm:justify-end\">\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n type=\"button\"\n aria-label={t('customers.companies.detail.actions.delete', 'Delete company')}\n onClick={() => {\n void onDelete()\n }}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={onSave}\n disabled={!isDirty || isSaving}\n >\n {t('customers.companies.detail.actions.save', 'Save')}\n </Button>\n </div>\n </div>\n </div>\n\n <CompanyTagsDialog\n open={manageTagsOpen}\n onClose={() => setManageTagsOpen(false)}\n entityId={company.id}\n companyOrganizationId={company.organizationId ?? null}\n companyData={{\n status: company.status,\n lifecycleStage: company.lifecycleStage,\n source: company.source,\n temperature: company.temperature,\n renewalQuarter: company.renewalQuarter,\n industry: profile?.industry ?? null,\n customFields: data.customFields,\n tags: data.tags,\n }}\n onSaved={() => {\n void invalidateCustomerDictionary(queryClient, 'statuses')\n void invalidateCustomerDictionary(queryClient, 'lifecycle-stages')\n void invalidateCustomerDictionary(queryClient, 'sources')\n void invalidateCustomerDictionary(queryClient, 'temperature')\n void invalidateCustomerDictionary(queryClient, 'renewal-quarters')\n onDataReload?.()\n }}\n />\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Phone, Mail, Trash2, Building2, Globe, Pencil, MapPin } from 'lucide-react'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { CompanyTagsDialog } from './CompanyTagsDialog'\nimport { ObjectHistoryButton } from './ObjectHistoryButton'\nimport { invalidateCustomerDictionary, useCustomerDictionary } from './hooks/useCustomerDictionary'\nimport { renderDictionaryIcon } from '../../../dictionaries/components/dictionaryAppearance'\nimport type { TagSummary } from './types'\nimport type { TagsSectionController } from '@open-mercato/ui/backend/detail'\nimport type { CompanyOverview } from '../formConfig'\nimport type { CustomerDictionaryMap } from '@open-mercato/core/modules/customers/lib/dictionaries'\nimport { formatFallbackLabel } from './utils'\n\nconst HEADER_ICON_BUTTON_CLASS = 'size-8 rounded-md'\n\ntype CompanyDetailHeaderProps = {\n data: CompanyOverview\n onTagsChange: (tags: TagSummary[]) => void\n tagsSectionControllerRef: React.RefObject<TagsSectionController | null>\n onSave: () => void\n onDelete: () => Promise<void>\n isDirty: boolean\n isSaving: boolean\n onFocusField?: (fieldName: string) => void\n onDataReload?: () => void\n}\n\nfunction CompanyDictionaryBadge({ value, map }: { value: string; map: CustomerDictionaryMap | undefined }) {\n const entry = map?.[value]\n const color = entry?.color ?? null\n const icon = entry?.icon ?? null\n const label = entry?.label ?? formatFallbackLabel(value)\n const colorStyle: React.CSSProperties | undefined = color\n ? { color, borderColor: color, backgroundColor: `${color}1A` }\n : undefined\n return (\n <Badge variant=\"outline\" className=\"rounded-sm gap-1.5 text-xs font-medium\" style={colorStyle}>\n {icon ? renderDictionaryIcon(icon, 'size-2.5') : null}\n {label}\n </Badge>\n )\n}\n\nexport function CompanyDetailHeader({\n data,\n onTagsChange,\n tagsSectionControllerRef,\n onSave,\n onDelete,\n isDirty,\n isSaving,\n onFocusField,\n onDataReload,\n}: CompanyDetailHeaderProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const [manageTagsOpen, setManageTagsOpen] = React.useState(false)\n const company = data.company\n const profile = data.profile\n const displayName = company.displayName || t('customers.companies.detail.untitled', 'Untitled')\n const visibleCustomTags = React.useMemo(\n () => (data.tags?.filter((tag) => !['status', 'lifecycle_stage', 'source'].includes(tag.id)).slice(0, 6) ?? []),\n [data.tags],\n )\n const hiddenCustomTagsCount = Math.max(\n 0,\n (data.tags?.filter((tag) => !['status', 'lifecycle_stage', 'source'].includes(tag.id)).length ?? 0) - visibleCustomTags.length,\n )\n\n const industryLabel = profile?.industry ?? null\n const sizeLabel = profile?.sizeBucket ?? null\n const subtitle = [industryLabel, sizeLabel ? t('customers.companies.detail.header.employees', '{count} employees', { count: sizeLabel }) : null].filter(Boolean).join(' \\u00b7 ')\n\n // Primary address for header\n const primaryAddress = React.useMemo(() => {\n const addresses = (data as Record<string, unknown>).addresses as Array<{ isPrimary?: boolean; city?: string; region?: string; postalCode?: string }> | undefined\n if (!addresses || !Array.isArray(addresses)) return null\n return addresses.find((a) => a.isPrimary) ?? addresses[0] ?? null\n }, [data])\n\n const locationText = React.useMemo(() => {\n if (!primaryAddress) return null\n return [primaryAddress.city, primaryAddress.region, primaryAddress.postalCode].filter(Boolean).join(', ')\n }, [primaryAddress])\n\n // Fetch dictionary maps for colored badge rendering\n const companyOrgId = company.organizationId ?? null\n const { data: statusDict } = useCustomerDictionary('statuses', 0, companyOrgId)\n const { data: lifecycleDict } = useCustomerDictionary('lifecycle-stages', 0, companyOrgId)\n const { data: sourceDict } = useCustomerDictionary('sources', 0, companyOrgId)\n const { data: temperatureDict } = useCustomerDictionary('temperature', 0, companyOrgId)\n const { data: renewalQuarterDict } = useCustomerDictionary('renewal-quarters', 0, companyOrgId)\n\n return (\n <div className=\"rounded-lg border bg-card\">\n {/* Top row: avatar + company info + account manager + actions */}\n <div className=\"flex flex-col gap-4 px-6 pt-6 pb-3 sm:flex-row sm:items-start sm:gap-5\">\n {/* Avatar */}\n <div className=\"flex size-18 shrink-0 items-center justify-center rounded-full bg-muted\">\n <Building2 className=\"size-7 text-muted-foreground\" />\n </div>\n\n {/* Company info */}\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"truncate text-2xl font-bold text-foreground\">{displayName}</h1>\n {subtitle && (\n <p className=\"mt-0.5 text-sm text-muted-foreground\">{subtitle}</p>\n )}\n\n {/* Contact row */}\n <div className=\"mt-1.5 flex flex-wrap items-center gap-x-5 gap-y-1 text-sm text-muted-foreground\">\n {company.primaryPhone && (\n <span className=\"inline-flex items-center gap-1.5\">\n <Phone className=\"size-3.5\" />\n <a href={`tel:${company.primaryPhone}`} className=\"hover:text-foreground\">{company.primaryPhone}</a>\n </span>\n )}\n {company.primaryEmail && (\n <span className=\"inline-flex items-center gap-1.5\">\n <Mail className=\"size-3.5\" />\n <a href={`mailto:${company.primaryEmail}`} className=\"hover:text-foreground\">{company.primaryEmail}</a>\n </span>\n )}\n {profile?.websiteUrl && (\n <a href={profile.websiteUrl.startsWith('http') ? profile.websiteUrl : `https://${profile.websiteUrl}`} target=\"_blank\" rel=\"noreferrer\" className=\"inline-flex items-center gap-1.5 hover:text-foreground\">\n <Globe className=\"size-3.5\" />\n {profile.websiteUrl}\n </a>\n )}\n {locationText && (\n <span className=\"inline-flex items-center gap-1.5\">\n <MapPin className=\"size-3.5\" />\n {locationText}\n </span>\n )}\n </div>\n\n {/* Status badges + temperature + renewal quarter + inline tags */}\n <div className=\"mt-2.5 flex flex-wrap items-center gap-1.5\">\n {company.status && (\n <CompanyDictionaryBadge value={company.status} map={statusDict?.map} />\n )}\n {company.lifecycleStage && (\n <CompanyDictionaryBadge value={company.lifecycleStage} map={lifecycleDict?.map} />\n )}\n {company.source && (\n <CompanyDictionaryBadge value={company.source} map={sourceDict?.map} />\n )}\n {company.temperature && (\n <CompanyDictionaryBadge value={company.temperature} map={temperatureDict?.map} />\n )}\n {company.renewalQuarter && (\n <CompanyDictionaryBadge value={company.renewalQuarter} map={renewalQuarterDict?.map} />\n )}\n {visibleCustomTags.map((tag) => {\n const colorStyle: React.CSSProperties | undefined = tag.color\n ? { color: tag.color, borderColor: tag.color, backgroundColor: `${tag.color}1A` }\n : undefined\n return (\n <Badge\n key={tag.id}\n variant=\"outline\"\n className=\"rounded-sm gap-1.5 text-xs font-medium\"\n style={colorStyle}\n >\n {tag.label}\n </Badge>\n )\n })}\n {hiddenCustomTagsCount > 0 ? (\n <Badge variant=\"outline\" className=\"rounded-sm gap-1.5 text-xs font-medium\">\n +{hiddenCustomTagsCount} {t('customers.companies.detail.header.more', 'more')}\n </Badge>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto rounded-sm px-2 py-1 text-xs font-medium text-muted-foreground hover:text-foreground\"\n onClick={() => setManageTagsOpen(true)}\n >\n <Pencil className=\"mr-1 size-3\" />\n {t('customers.companies.detail.actions.manageTags', 'Edit tags')}\n </Button>\n </div>\n </div>\n\n {/* Right side: actions */}\n <div className=\"flex w-full shrink-0 flex-col items-start gap-3 sm:w-auto sm:items-end\">\n <div className=\"flex w-full flex-wrap items-center justify-start gap-2 sm:w-auto sm:justify-end\">\n <SendObjectMessageDialog\n object={{\n entityModule: 'customers',\n entityType: 'company',\n entityId: company.id,\n previewData: {\n title: displayName,\n subtitle: company.primaryEmail ?? profile?.websiteUrl ?? undefined,\n },\n }}\n viewHref={`/backend/customers/companies-v2/${company.id}`}\n buttonVariant=\"outline\"\n buttonSize=\"icon\"\n buttonClassName={HEADER_ICON_BUTTON_CLASS}\n buttonLabel={t('customers.companies.detail.actions.sendMessage', 'Send message')}\n />\n <ObjectHistoryButton\n resourceKind=\"customers.company\"\n resourceId={company.id}\n organizationId={company.organizationId ?? undefined}\n />\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n type=\"button\"\n aria-label={t('customers.companies.detail.actions.delete', 'Delete company')}\n onClick={() => {\n void onDelete()\n }}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={onSave}\n disabled={!isDirty || isSaving}\n >\n {t('customers.companies.detail.actions.save', 'Save')}\n </Button>\n </div>\n </div>\n </div>\n\n <CompanyTagsDialog\n open={manageTagsOpen}\n onClose={() => setManageTagsOpen(false)}\n entityId={company.id}\n companyOrganizationId={company.organizationId ?? null}\n companyData={{\n status: company.status,\n lifecycleStage: company.lifecycleStage,\n source: company.source,\n temperature: company.temperature,\n renewalQuarter: company.renewalQuarter,\n industry: profile?.industry ?? null,\n customFields: data.customFields,\n tags: data.tags,\n }}\n onSaved={() => {\n void invalidateCustomerDictionary(queryClient, 'statuses')\n void invalidateCustomerDictionary(queryClient, 'lifecycle-stages')\n void invalidateCustomerDictionary(queryClient, 'sources')\n void invalidateCustomerDictionary(queryClient, 'temperature')\n void invalidateCustomerDictionary(queryClient, 'renewal-quarters')\n onDataReload?.()\n }}\n />\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -6,7 +6,9 @@ import { cn } from "@open-mercato/shared/lib/utils";
6
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
7
7
  import { Button } from "@open-mercato/ui/primitives/button";
8
8
  import { IconButton } from "@open-mercato/ui/primitives/icon-button";
9
+ import { SendObjectMessageDialog } from "@open-mercato/ui/backend/messages";
9
10
  import { Popover, PopoverContent, PopoverTrigger } from "@open-mercato/ui/primitives/popover";
11
+ import { ObjectHistoryButton } from "./ObjectHistoryButton.js";
10
12
  import { useCustomerDictionary } from "./hooks/useCustomerDictionary.js";
11
13
  import { formatFallbackLabel } from "./utils.js";
12
14
  import { isTerminalPipelineOutcomeLabel } from "./pipelineStageUtils.js";
@@ -34,6 +36,7 @@ const headerChipDotVariantClasses = {
34
36
  error: "bg-status-error-icon",
35
37
  neutral: "bg-status-neutral-icon"
36
38
  };
39
+ const HEADER_ICON_BUTTON_CLASS = "size-8 rounded-md";
37
40
  function HeaderChip({
38
41
  children,
39
42
  icon,
@@ -160,6 +163,7 @@ function DealDetailHeader({
160
163
  const showStatusChip = statusLabel && (!deal.closureOutcome || !isTerminalPipelineOutcomeLabel(deal.status));
161
164
  const pipelineBadgeLabel = pipelineName ?? null;
162
165
  const canMoveStage = stageOptions.length > 0 && !deal.closureOutcome && typeof onStageChange === "function";
166
+ const messageSubtitle = React.useMemo(() => [companyLabel, amountLabel].filter(Boolean).join(" \xB7 ") || statusLabel || void 0, [amountLabel, companyLabel, statusLabel]);
163
167
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5 xl:flex-row xl:items-start xl:justify-between", children: [
164
168
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 gap-5", children: [
165
169
  /* @__PURE__ */ jsx("div", { className: "flex size-16 shrink-0 items-center justify-center rounded-lg border border-border bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx(FileText, { className: "size-7" }) }),
@@ -200,6 +204,34 @@ function DealDetailHeader({
200
204
  isSaving: isStageSaving
201
205
  }
202
206
  ) : null,
207
+ /* @__PURE__ */ jsx(
208
+ SendObjectMessageDialog,
209
+ {
210
+ object: {
211
+ entityModule: "customers",
212
+ entityType: "deal",
213
+ entityId: deal.id,
214
+ previewData: {
215
+ title: deal.title || t("customers.deals.detail.untitled", "Untitled deal"),
216
+ subtitle: messageSubtitle
217
+ }
218
+ },
219
+ viewHref: `/backend/customers/deals/${deal.id}`,
220
+ buttonVariant: "outline",
221
+ buttonSize: "icon",
222
+ buttonClassName: HEADER_ICON_BUTTON_CLASS,
223
+ buttonLabel: t("customers.deals.detail.actions.sendMessage", "Send message")
224
+ }
225
+ ),
226
+ /* @__PURE__ */ jsx(
227
+ ObjectHistoryButton,
228
+ {
229
+ resourceKind: "customers.deal",
230
+ resourceId: deal.id,
231
+ organizationId: deal.organizationId ?? void 0,
232
+ includeRelated: true
233
+ }
234
+ ),
203
235
  /* @__PURE__ */ jsx(
204
236
  IconButton,
205
237
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/DealDetailHeader.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Building2, CalendarClock, Check, ChevronDown, FileText, Save, Trash2, Users, Workflow } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport { useCustomerDictionary } from './hooks/useCustomerDictionary'\nimport { formatFallbackLabel } from './utils'\nimport { isTerminalPipelineOutcomeLabel } from './pipelineStageUtils'\n\ntype DealAssociation = {\n id: string\n label: string\n subtitle: string | null\n kind: 'person' | 'company'\n}\n\ntype DealDetailHeaderProps = {\n deal: {\n id: string\n title: string\n status: string | null\n pipelineStage: string | null\n valueAmount: string | null\n valueCurrency: string | null\n expectedCloseAt: string | null\n createdAt: string\n closureOutcome: 'won' | 'lost' | null\n organizationId: string | null\n }\n owner?: { id: string; name: string; email: string } | null\n people: DealAssociation[]\n companies: DealAssociation[]\n pipelineName?: string | null\n stageOptions?: Array<{ id: string; label: string; order: number }>\n currentStageId?: string | null\n onStageChange?: (stageId: string) => Promise<void> | void\n isStageSaving?: boolean\n onSave: () => void\n onDelete: () => Promise<void> | void\n isDirty: boolean\n isSaving: boolean\n}\n\nfunction formatCurrency(amount: string | null, currency: string | null): string | null {\n if (!amount) return null\n const parsed = Number(amount)\n if (!Number.isFinite(parsed)) return currency ? `${amount} ${currency}` : amount\n if (!currency) return parsed.toLocaleString()\n try {\n return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(parsed)\n } catch {\n return `${parsed.toLocaleString()} ${currency}`\n }\n}\n\nfunction formatDate(value: string | null): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })\n}\n\ntype HeaderChipVariant = 'info' | 'warning' | 'success' | 'error' | 'neutral'\n\nconst headerChipDotVariantClasses: Record<HeaderChipVariant, string> = {\n info: 'bg-status-info-icon',\n warning: 'bg-status-warning-icon',\n success: 'bg-status-success-icon',\n error: 'bg-status-error-icon',\n neutral: 'bg-status-neutral-icon',\n}\n\nfunction HeaderChip({\n children,\n icon,\n variant,\n}: {\n children: React.ReactNode\n icon?: React.ReactNode\n variant?: HeaderChipVariant\n}) {\n return (\n <span className=\"inline-flex items-center gap-1.5 rounded-sm bg-muted px-2 py-1 text-xs font-medium leading-none text-muted-foreground\">\n {variant ? <span className={cn('size-2 rounded-full', headerChipDotVariantClasses[variant])} /> : null}\n {icon ? <span className=\"text-muted-foreground\">{icon}</span> : null}\n {children}\n </span>\n )\n}\n\nfunction StageActionButton({\n stageOptions,\n currentStageId,\n onStageChange,\n disabled,\n isSaving,\n}: {\n stageOptions: Array<{ id: string; label: string; order: number }>\n currentStageId: string | null\n onStageChange: (stageId: string) => Promise<void> | void\n disabled: boolean\n isSaving: boolean\n}) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const sortedStages = React.useMemo(\n () =>\n [...stageOptions]\n .filter((stage) => !isTerminalPipelineOutcomeLabel(stage.label))\n .sort((left, right) => left.order - right.order),\n [stageOptions],\n )\n const currentStage = React.useMemo(\n () => sortedStages.find((stage) => stage.id === currentStageId) ?? null,\n [currentStageId, sortedStages],\n )\n\n if (!sortedStages.length) return null\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={disabled || isSaving}\n className=\"h-8 max-w-[13rem] rounded-md px-3\"\n >\n <Workflow className=\"size-4\" />\n <span className=\"truncate\">\n {currentStage?.label ?? t('customers.deals.detail.actions.moveStage', 'Move stage')}\n </span>\n <ChevronDown className=\"size-4\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-64 p-1\">\n <div className=\"border-b border-border px-3 py-2\">\n <div className=\"text-xs font-semibold text-foreground\">\n {t('customers.deals.detail.stageMenu.title', 'Update pipeline stage')}\n </div>\n <div className=\"mt-0.5 text-xs text-muted-foreground\">\n {t('customers.deals.detail.stageMenu.description', 'Move the deal forward without opening the form.')}\n </div>\n </div>\n <div className=\"py-1\">\n {sortedStages.map((stage) => {\n const isCurrent = stage.id === currentStageId\n return (\n <Button\n key={stage.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={disabled || isSaving || isCurrent}\n className=\"h-auto w-full justify-between rounded-md px-3 py-2 text-left\"\n onClick={() => {\n setOpen(false)\n if (!isCurrent) {\n void onStageChange(stage.id)\n }\n }}\n >\n <span className=\"min-w-0 truncate text-sm font-medium\">\n {stage.label}\n </span>\n {isCurrent ? <Check className=\"size-4 text-foreground\" /> : null}\n </Button>\n )\n })}\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport function DealDetailHeader({\n deal,\n owner,\n people,\n companies,\n pipelineName,\n stageOptions = [],\n currentStageId = null,\n onStageChange,\n isStageSaving = false,\n onSave,\n onDelete,\n isDirty,\n isSaving,\n}: DealDetailHeaderProps) {\n const t = useT()\n const amountLabel = React.useMemo(\n () => formatCurrency(deal.valueAmount, deal.valueCurrency),\n [deal.valueAmount, deal.valueCurrency],\n )\n const createdAtLabel = React.useMemo(() => formatDate(deal.createdAt), [deal.createdAt])\n const expectedCloseLabel = React.useMemo(() => formatDate(deal.expectedCloseAt), [deal.expectedCloseAt])\n const { data: statusDictionary } = useCustomerDictionary('deal-statuses', 0, deal.organizationId ?? null)\n const statusEntry = deal.status ? statusDictionary?.map?.[deal.status] : null\n const companyLabel = React.useMemo(() => {\n const primaryCompany = companies[0]\n if (!primaryCompany) return null\n const extraCount = companies.length - 1\n return extraCount > 0 ? `${primaryCompany.label} +${extraCount}` : primaryCompany.label\n }, [companies])\n const timelineLabel = React.useMemo(() => {\n if (createdAtLabel && expectedCloseLabel) {\n return t('customers.deals.detail.header.timeline', 'Created {{created}} \u00B7 Expected close {{expected}}', {\n created: createdAtLabel,\n expected: expectedCloseLabel,\n })\n }\n if (createdAtLabel) {\n return t('customers.deals.detail.header.createdAt', 'Created {{date}}', { date: createdAtLabel })\n }\n if (expectedCloseLabel) {\n return t('customers.deals.detail.header.expectedClose', 'Expected close {{date}}', { date: expectedCloseLabel })\n }\n return null\n }, [createdAtLabel, expectedCloseLabel, t])\n const statusLabel = statusEntry?.label ?? (deal.status ? formatFallbackLabel(deal.status) : null)\n const showStatusChip = statusLabel && (!deal.closureOutcome || !isTerminalPipelineOutcomeLabel(deal.status))\n const pipelineBadgeLabel = pipelineName ?? null\n const canMoveStage = stageOptions.length > 0 && !deal.closureOutcome && typeof onStageChange === 'function'\n\n return (\n <div className=\"flex flex-col gap-5 xl:flex-row xl:items-start xl:justify-between\">\n <div className=\"flex min-w-0 gap-5\">\n <div className=\"flex size-16 shrink-0 items-center justify-center rounded-lg border border-border bg-muted text-muted-foreground\">\n <FileText className=\"size-7\" />\n </div>\n <div className=\"min-w-0 space-y-3\">\n <div className=\"flex flex-wrap items-baseline gap-2\">\n <h1 className=\"min-w-0 truncate text-2xl font-bold leading-tight text-foreground md:text-2xl\">\n {deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}\n </h1>\n <span className=\"rounded-sm bg-muted px-2 py-1 text-overline font-semibold uppercase tracking-[0.04em] text-muted-foreground\">\n {t('customers.deals.detail.badge.deal', 'Deal')}\n </span>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-muted-foreground\">\n {companyLabel ? (\n <span className=\"inline-flex items-center gap-1.5\">\n <Building2 className=\"size-3.5\" />\n {companyLabel}\n </span>\n ) : null}\n {timelineLabel ? (\n <span className=\"inline-flex items-center gap-1.5\">\n <CalendarClock className=\"size-3.5\" />\n {timelineLabel}\n </span>\n ) : null}\n {amountLabel ? (\n <span className=\"font-semibold text-foreground\">{amountLabel}</span>\n ) : null}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n {showStatusChip ? (\n <HeaderChip variant=\"info\">\n {statusLabel}\n </HeaderChip>\n ) : null}\n {deal.pipelineStage && !isTerminalPipelineOutcomeLabel(deal.pipelineStage) ? (\n <HeaderChip variant=\"warning\">\n {deal.pipelineStage}\n </HeaderChip>\n ) : null}\n {pipelineBadgeLabel ? (\n <HeaderChip icon={<Workflow className=\"size-3.5\" />}>\n {pipelineBadgeLabel}\n </HeaderChip>\n ) : null}\n {deal.closureOutcome ? (\n <HeaderChip variant={deal.closureOutcome === 'won' ? 'success' : 'error'}>\n {deal.closureOutcome === 'won'\n ? t('customers.deals.detail.badge.won', 'Won')\n : t('customers.deals.detail.badge.lost', 'Lost')}\n </HeaderChip>\n ) : null}\n {!companyLabel && people.length > 0 ? (\n <HeaderChip icon={<Users className=\"size-3.5\" />}>\n {people[0]?.label}\n </HeaderChip>\n ) : null}\n {owner && !pipelineBadgeLabel ? (\n <HeaderChip icon={<Workflow className=\"size-3.5\" />}>\n {owner.name}\n </HeaderChip>\n ) : null}\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 xl:justify-end\">\n {canMoveStage ? (\n <StageActionButton\n stageOptions={stageOptions}\n currentStageId={currentStageId}\n onStageChange={onStageChange}\n disabled={isDirty || isSaving}\n isSaving={isStageSaving}\n />\n ) : null}\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n type=\"button\"\n aria-label={t('customers.deals.detail.actions.delete', 'Delete')}\n className=\"h-8 w-8 rounded-md\"\n onClick={() => {\n void onDelete()\n }}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={onSave}\n disabled={!isDirty || isSaving}\n className=\"h-8 rounded-md px-3\"\n >\n <Save className=\"size-4\" />\n {t('customers.deals.detail.actions.save', 'Save')}\n </Button>\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Building2, CalendarClock, Check, ChevronDown, FileText, Save, Trash2, Users, Workflow } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport { ObjectHistoryButton } from './ObjectHistoryButton'\nimport { useCustomerDictionary } from './hooks/useCustomerDictionary'\nimport { formatFallbackLabel } from './utils'\nimport { isTerminalPipelineOutcomeLabel } from './pipelineStageUtils'\n\ntype DealAssociation = {\n id: string\n label: string\n subtitle: string | null\n kind: 'person' | 'company'\n}\n\ntype DealDetailHeaderProps = {\n deal: {\n id: string\n title: string\n status: string | null\n pipelineStage: string | null\n valueAmount: string | null\n valueCurrency: string | null\n expectedCloseAt: string | null\n createdAt: string\n closureOutcome: 'won' | 'lost' | null\n organizationId: string | null\n }\n owner?: { id: string; name: string; email: string } | null\n people: DealAssociation[]\n companies: DealAssociation[]\n pipelineName?: string | null\n stageOptions?: Array<{ id: string; label: string; order: number }>\n currentStageId?: string | null\n onStageChange?: (stageId: string) => Promise<void> | void\n isStageSaving?: boolean\n onSave: () => void\n onDelete: () => Promise<void> | void\n isDirty: boolean\n isSaving: boolean\n}\n\nfunction formatCurrency(amount: string | null, currency: string | null): string | null {\n if (!amount) return null\n const parsed = Number(amount)\n if (!Number.isFinite(parsed)) return currency ? `${amount} ${currency}` : amount\n if (!currency) return parsed.toLocaleString()\n try {\n return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(parsed)\n } catch {\n return `${parsed.toLocaleString()} ${currency}`\n }\n}\n\nfunction formatDate(value: string | null): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })\n}\n\ntype HeaderChipVariant = 'info' | 'warning' | 'success' | 'error' | 'neutral'\n\nconst headerChipDotVariantClasses: Record<HeaderChipVariant, string> = {\n info: 'bg-status-info-icon',\n warning: 'bg-status-warning-icon',\n success: 'bg-status-success-icon',\n error: 'bg-status-error-icon',\n neutral: 'bg-status-neutral-icon',\n}\n\nconst HEADER_ICON_BUTTON_CLASS = 'size-8 rounded-md'\n\nfunction HeaderChip({\n children,\n icon,\n variant,\n}: {\n children: React.ReactNode\n icon?: React.ReactNode\n variant?: HeaderChipVariant\n}) {\n return (\n <span className=\"inline-flex items-center gap-1.5 rounded-sm bg-muted px-2 py-1 text-xs font-medium leading-none text-muted-foreground\">\n {variant ? <span className={cn('size-2 rounded-full', headerChipDotVariantClasses[variant])} /> : null}\n {icon ? <span className=\"text-muted-foreground\">{icon}</span> : null}\n {children}\n </span>\n )\n}\n\nfunction StageActionButton({\n stageOptions,\n currentStageId,\n onStageChange,\n disabled,\n isSaving,\n}: {\n stageOptions: Array<{ id: string; label: string; order: number }>\n currentStageId: string | null\n onStageChange: (stageId: string) => Promise<void> | void\n disabled: boolean\n isSaving: boolean\n}) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const sortedStages = React.useMemo(\n () =>\n [...stageOptions]\n .filter((stage) => !isTerminalPipelineOutcomeLabel(stage.label))\n .sort((left, right) => left.order - right.order),\n [stageOptions],\n )\n const currentStage = React.useMemo(\n () => sortedStages.find((stage) => stage.id === currentStageId) ?? null,\n [currentStageId, sortedStages],\n )\n\n if (!sortedStages.length) return null\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={disabled || isSaving}\n className=\"h-8 max-w-[13rem] rounded-md px-3\"\n >\n <Workflow className=\"size-4\" />\n <span className=\"truncate\">\n {currentStage?.label ?? t('customers.deals.detail.actions.moveStage', 'Move stage')}\n </span>\n <ChevronDown className=\"size-4\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-64 p-1\">\n <div className=\"border-b border-border px-3 py-2\">\n <div className=\"text-xs font-semibold text-foreground\">\n {t('customers.deals.detail.stageMenu.title', 'Update pipeline stage')}\n </div>\n <div className=\"mt-0.5 text-xs text-muted-foreground\">\n {t('customers.deals.detail.stageMenu.description', 'Move the deal forward without opening the form.')}\n </div>\n </div>\n <div className=\"py-1\">\n {sortedStages.map((stage) => {\n const isCurrent = stage.id === currentStageId\n return (\n <Button\n key={stage.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={disabled || isSaving || isCurrent}\n className=\"h-auto w-full justify-between rounded-md px-3 py-2 text-left\"\n onClick={() => {\n setOpen(false)\n if (!isCurrent) {\n void onStageChange(stage.id)\n }\n }}\n >\n <span className=\"min-w-0 truncate text-sm font-medium\">\n {stage.label}\n </span>\n {isCurrent ? <Check className=\"size-4 text-foreground\" /> : null}\n </Button>\n )\n })}\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport function DealDetailHeader({\n deal,\n owner,\n people,\n companies,\n pipelineName,\n stageOptions = [],\n currentStageId = null,\n onStageChange,\n isStageSaving = false,\n onSave,\n onDelete,\n isDirty,\n isSaving,\n}: DealDetailHeaderProps) {\n const t = useT()\n const amountLabel = React.useMemo(\n () => formatCurrency(deal.valueAmount, deal.valueCurrency),\n [deal.valueAmount, deal.valueCurrency],\n )\n const createdAtLabel = React.useMemo(() => formatDate(deal.createdAt), [deal.createdAt])\n const expectedCloseLabel = React.useMemo(() => formatDate(deal.expectedCloseAt), [deal.expectedCloseAt])\n const { data: statusDictionary } = useCustomerDictionary('deal-statuses', 0, deal.organizationId ?? null)\n const statusEntry = deal.status ? statusDictionary?.map?.[deal.status] : null\n const companyLabel = React.useMemo(() => {\n const primaryCompany = companies[0]\n if (!primaryCompany) return null\n const extraCount = companies.length - 1\n return extraCount > 0 ? `${primaryCompany.label} +${extraCount}` : primaryCompany.label\n }, [companies])\n const timelineLabel = React.useMemo(() => {\n if (createdAtLabel && expectedCloseLabel) {\n return t('customers.deals.detail.header.timeline', 'Created {{created}} \u00B7 Expected close {{expected}}', {\n created: createdAtLabel,\n expected: expectedCloseLabel,\n })\n }\n if (createdAtLabel) {\n return t('customers.deals.detail.header.createdAt', 'Created {{date}}', { date: createdAtLabel })\n }\n if (expectedCloseLabel) {\n return t('customers.deals.detail.header.expectedClose', 'Expected close {{date}}', { date: expectedCloseLabel })\n }\n return null\n }, [createdAtLabel, expectedCloseLabel, t])\n const statusLabel = statusEntry?.label ?? (deal.status ? formatFallbackLabel(deal.status) : null)\n const showStatusChip = statusLabel && (!deal.closureOutcome || !isTerminalPipelineOutcomeLabel(deal.status))\n const pipelineBadgeLabel = pipelineName ?? null\n const canMoveStage = stageOptions.length > 0 && !deal.closureOutcome && typeof onStageChange === 'function'\n const messageSubtitle = React.useMemo(() => (\n [companyLabel, amountLabel].filter(Boolean).join(' \u00B7 ') || statusLabel || undefined\n ), [amountLabel, companyLabel, statusLabel])\n\n return (\n <div className=\"flex flex-col gap-5 xl:flex-row xl:items-start xl:justify-between\">\n <div className=\"flex min-w-0 gap-5\">\n <div className=\"flex size-16 shrink-0 items-center justify-center rounded-lg border border-border bg-muted text-muted-foreground\">\n <FileText className=\"size-7\" />\n </div>\n <div className=\"min-w-0 space-y-3\">\n <div className=\"flex flex-wrap items-baseline gap-2\">\n <h1 className=\"min-w-0 truncate text-2xl font-bold leading-tight text-foreground md:text-2xl\">\n {deal.title || t('customers.deals.detail.untitled', 'Untitled deal')}\n </h1>\n <span className=\"rounded-sm bg-muted px-2 py-1 text-overline font-semibold uppercase tracking-[0.04em] text-muted-foreground\">\n {t('customers.deals.detail.badge.deal', 'Deal')}\n </span>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-muted-foreground\">\n {companyLabel ? (\n <span className=\"inline-flex items-center gap-1.5\">\n <Building2 className=\"size-3.5\" />\n {companyLabel}\n </span>\n ) : null}\n {timelineLabel ? (\n <span className=\"inline-flex items-center gap-1.5\">\n <CalendarClock className=\"size-3.5\" />\n {timelineLabel}\n </span>\n ) : null}\n {amountLabel ? (\n <span className=\"font-semibold text-foreground\">{amountLabel}</span>\n ) : null}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n {showStatusChip ? (\n <HeaderChip variant=\"info\">\n {statusLabel}\n </HeaderChip>\n ) : null}\n {deal.pipelineStage && !isTerminalPipelineOutcomeLabel(deal.pipelineStage) ? (\n <HeaderChip variant=\"warning\">\n {deal.pipelineStage}\n </HeaderChip>\n ) : null}\n {pipelineBadgeLabel ? (\n <HeaderChip icon={<Workflow className=\"size-3.5\" />}>\n {pipelineBadgeLabel}\n </HeaderChip>\n ) : null}\n {deal.closureOutcome ? (\n <HeaderChip variant={deal.closureOutcome === 'won' ? 'success' : 'error'}>\n {deal.closureOutcome === 'won'\n ? t('customers.deals.detail.badge.won', 'Won')\n : t('customers.deals.detail.badge.lost', 'Lost')}\n </HeaderChip>\n ) : null}\n {!companyLabel && people.length > 0 ? (\n <HeaderChip icon={<Users className=\"size-3.5\" />}>\n {people[0]?.label}\n </HeaderChip>\n ) : null}\n {owner && !pipelineBadgeLabel ? (\n <HeaderChip icon={<Workflow className=\"size-3.5\" />}>\n {owner.name}\n </HeaderChip>\n ) : null}\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 xl:justify-end\">\n {canMoveStage ? (\n <StageActionButton\n stageOptions={stageOptions}\n currentStageId={currentStageId}\n onStageChange={onStageChange}\n disabled={isDirty || isSaving}\n isSaving={isStageSaving}\n />\n ) : null}\n <SendObjectMessageDialog\n object={{\n entityModule: 'customers',\n entityType: 'deal',\n entityId: deal.id,\n previewData: {\n title: deal.title || t('customers.deals.detail.untitled', 'Untitled deal'),\n subtitle: messageSubtitle,\n },\n }}\n viewHref={`/backend/customers/deals/${deal.id}`}\n buttonVariant=\"outline\"\n buttonSize=\"icon\"\n buttonClassName={HEADER_ICON_BUTTON_CLASS}\n buttonLabel={t('customers.deals.detail.actions.sendMessage', 'Send message')}\n />\n <ObjectHistoryButton\n resourceKind=\"customers.deal\"\n resourceId={deal.id}\n organizationId={deal.organizationId ?? undefined}\n includeRelated\n />\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n type=\"button\"\n aria-label={t('customers.deals.detail.actions.delete', 'Delete')}\n className=\"h-8 w-8 rounded-md\"\n onClick={() => {\n void onDelete()\n }}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={onSave}\n disabled={!isDirty || isSaving}\n className=\"h-8 rounded-md px-3\"\n >\n <Save className=\"size-4\" />\n {t('customers.deals.detail.actions.save', 'Save')}\n </Button>\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }