@open-mercato/core 0.6.4-develop.4178.1.aad9ddaa95 → 0.6.4-develop.4210.1.d412061cfe

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 (99) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/api_docs/frontend/docs/api/Explorer.js +14 -14
  3. package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
  4. package/dist/modules/attachments/components/AttachmentContentPreview.js +2 -2
  5. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  6. package/dist/modules/audit_logs/backend/audit-logs/page.js +3 -3
  7. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  8. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +3 -7
  9. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
  10. package/dist/modules/customers/components/detail/ActivityCard.js +2 -2
  11. package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
  12. package/dist/modules/customers/components/detail/AssignRoleDialog.js +34 -49
  13. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
  14. package/dist/modules/customers/components/detail/ChangelogEntryRow.js +2 -2
  15. package/dist/modules/customers/components/detail/ChangelogEntryRow.js.map +2 -2
  16. package/dist/modules/customers/components/detail/CompanyDetailHeader.js +10 -1
  17. package/dist/modules/customers/components/detail/CompanyDetailHeader.js.map +2 -2
  18. package/dist/modules/customers/components/detail/DealLinkedEntitiesTab.js +7 -51
  19. package/dist/modules/customers/components/detail/DealLinkedEntitiesTab.js.map +2 -2
  20. package/dist/modules/customers/components/detail/DetailTabsLayout.js +1 -1
  21. package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
  22. package/dist/modules/customers/components/detail/ManageTagsDialog.js +25 -33
  23. package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
  24. package/dist/modules/customers/components/detail/PersonCard.js +3 -2
  25. package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
  26. package/dist/modules/customers/components/detail/PersonDetailHeader.js +3 -2
  27. package/dist/modules/customers/components/detail/PersonDetailHeader.js.map +2 -2
  28. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -5
  29. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
  30. package/dist/modules/customers/components/detail/utils.js +0 -7
  31. package/dist/modules/customers/components/detail/utils.js.map +2 -2
  32. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +3 -8
  33. package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
  34. package/dist/modules/dictionaries/components/AppearanceSelector.js +3 -4
  35. package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
  36. package/dist/modules/integrations/backend/integrations/[id]/page.js +17 -17
  37. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  38. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +1 -1
  39. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
  40. package/dist/modules/planner/components/AvailabilityRulesEditor.js +65 -1
  41. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  42. package/dist/modules/planner/lib/deleteAvailabilityRuleSet.js +20 -0
  43. package/dist/modules/planner/lib/deleteAvailabilityRuleSet.js.map +7 -0
  44. package/dist/modules/resources/backend/resources/resources/[id]/page.js +2 -2
  45. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  46. package/dist/modules/sales/api/quotes/accept/route.js +14 -37
  47. package/dist/modules/sales/api/quotes/accept/route.js.map +3 -3
  48. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +1 -1
  49. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  50. package/dist/modules/sales/backend/sales/documents/[id]/page.js +1 -1
  51. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  52. package/dist/modules/sales/commands/documents.js +6 -2
  53. package/dist/modules/sales/commands/documents.js.map +2 -2
  54. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +3 -2
  55. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  56. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +1 -1
  57. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  58. package/dist/modules/translations/components/TranslationDrawerAction.js +27 -65
  59. package/dist/modules/translations/components/TranslationDrawerAction.js.map +2 -2
  60. package/dist/modules/translations/components/TranslationManager.js +2 -2
  61. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  62. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +54 -92
  63. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
  64. package/package.json +7 -7
  65. package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +14 -14
  66. package/src/modules/attachments/components/AttachmentContentPreview.tsx +2 -2
  67. package/src/modules/audit_logs/backend/audit-logs/page.tsx +3 -3
  68. package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -8
  69. package/src/modules/customers/components/detail/ActivityCard.tsx +2 -4
  70. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +28 -55
  71. package/src/modules/customers/components/detail/ChangelogEntryRow.tsx +6 -4
  72. package/src/modules/customers/components/detail/CompanyDetailHeader.tsx +7 -3
  73. package/src/modules/customers/components/detail/DealLinkedEntitiesTab.tsx +11 -49
  74. package/src/modules/customers/components/detail/DetailTabsLayout.tsx +1 -1
  75. package/src/modules/customers/components/detail/ManageTagsDialog.tsx +27 -36
  76. package/src/modules/customers/components/detail/PersonCard.tsx +3 -4
  77. package/src/modules/customers/components/detail/PersonDetailHeader.tsx +3 -4
  78. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -7
  79. package/src/modules/customers/components/detail/utils.ts +0 -7
  80. package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +4 -9
  81. package/src/modules/dictionaries/components/AppearanceSelector.tsx +3 -4
  82. package/src/modules/integrations/backend/integrations/[id]/page.tsx +21 -21
  83. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +1 -1
  84. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +62 -0
  85. package/src/modules/planner/lib/deleteAvailabilityRuleSet.ts +35 -0
  86. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +2 -2
  87. package/src/modules/sales/api/quotes/accept/route.ts +22 -38
  88. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +1 -1
  89. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +1 -1
  90. package/src/modules/sales/commands/documents.ts +16 -2
  91. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +3 -2
  92. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +1 -1
  93. package/src/modules/staff/i18n/de.json +5 -0
  94. package/src/modules/staff/i18n/en.json +5 -0
  95. package/src/modules/staff/i18n/es.json +5 -0
  96. package/src/modules/staff/i18n/pl.json +5 -0
  97. package/src/modules/translations/components/TranslationDrawerAction.tsx +31 -66
  98. package/src/modules/translations/components/TranslationManager.tsx +2 -2
  99. package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +53 -84
@@ -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 Link from 'next/link'\nimport { Search, Check, CheckCheck, Settings2 } from 'lucide-react'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\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 <EmptyState\n size=\"sm\"\n icon={<CheckCheck className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t(\n 'customers.roles.dialog.noAvailableRoles',\n 'All available role types are already assigned.',\n )}\n />\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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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Search, Check, CheckCheck, Settings2 } from 'lucide-react'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { StepIndicator, type StepIndicatorStep } from '@open-mercato/ui/primitives/step-indicator'\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'\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 <Avatar\n label={selectedUser.displayName}\n size=\"lg\"\n className=\"bg-background text-foreground\"\n />\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 <StepIndicator\n className=\"justify-center\"\n steps={[\n {\n id: '1',\n label: t('customers.roles.dialog.step1', 'Role type'),\n status: step > 1 ? 'complete' : step === 1 ? 'current' : 'pending',\n },\n {\n id: '2',\n label: t('customers.roles.dialog.step2', 'Select person'),\n status: step > 2 ? 'complete' : step === 2 ? 'current' : 'pending',\n },\n {\n id: '3',\n label: t('customers.roles.dialog.step3', 'Confirm'),\n status: step === 3 ? 'current' : 'pending',\n },\n ] satisfies StepIndicatorStep[]}\n />\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 <EmptyState\n size=\"sm\"\n icon={<CheckCheck className=\"h-8 w-8\" aria-hidden=\"true\" />}\n title={t(\n 'customers.roles.dialog.noAvailableRoles',\n 'All available role types are already assigned.',\n )}\n />\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 <Avatar label={user.displayName} size=\"lg\" variant=\"monochrome\" />\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\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Avatar } from "@open-mercato/ui/primitives/avatar";
3
4
  import { Badge } from "@open-mercato/ui/primitives/badge";
4
5
  import { Settings2, SquarePen, Plus, Trash2, UserRoundPlus, ArrowRight } from "lucide-react";
5
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
- import { getInitials } from "./utils.js";
7
7
  const ACTION_ICONS = {
8
8
  create: Plus,
9
9
  edit: SquarePen,
@@ -41,7 +41,7 @@ function ChangelogEntryRow({ entry }) {
41
41
  /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: relativeLabel })
42
42
  ] }),
43
43
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
44
- /* @__PURE__ */ jsx("div", { className: "flex size-7 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold text-muted-foreground", children: entry.actorUserId ? getInitials(actorLabel) : /* @__PURE__ */ jsx(Settings2, { className: "size-3.5" }) }),
44
+ entry.actorUserId ? /* @__PURE__ */ jsx(Avatar, { label: actorLabel, size: "sm", variant: "monochrome" }) : /* @__PURE__ */ jsx(Avatar, { label: "", icon: /* @__PURE__ */ jsx(Settings2, {}), size: "sm", variant: "monochrome" }),
45
45
  /* @__PURE__ */ jsx("span", { className: "truncate pt-1 text-sm text-foreground", children: actorLabel })
46
46
  ] }),
47
47
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 pt-0.5 text-sm text-foreground", children: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/ChangelogEntryRow.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Settings2, SquarePen, Plus, Trash2, UserRoundPlus, ArrowRight } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getInitials } from './utils'\n\ntype ChangelogActionType = 'create' | 'edit' | 'delete' | 'assign' | 'system'\ntype ChangelogSource = 'ui' | 'api' | 'system'\n\ntype ChangelogEntryRowProps = {\n entry: {\n id: string\n createdAt: string\n actorUserId: string | null\n actorUserName: string | null\n actionType: ChangelogActionType\n source: ChangelogSource\n description: string | null\n changes: Array<{\n fieldName: string\n oldValue: string\n newValue: string\n }>\n }\n}\n\nconst ACTION_ICONS: Record<ChangelogActionType, React.ComponentType<{ className?: string }>> = {\n create: Plus,\n edit: SquarePen,\n delete: Trash2,\n assign: UserRoundPlus,\n system: Settings2,\n}\n\nfunction formatFieldLabel(fieldName: string): string {\n return fieldName\n .replace(/\\./g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^\\w/, (char) => char.toUpperCase())\n}\n\nfunction formatRelativeTime(value: string): string {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n const diffMs = date.getTime() - Date.now()\n const diffMinutes = Math.round(diffMs / 60000)\n const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })\n\n if (Math.abs(diffMinutes) < 60) return formatter.format(diffMinutes, 'minute')\n\n const diffHours = Math.round(diffMinutes / 60)\n if (Math.abs(diffHours) < 24) return formatter.format(diffHours, 'hour')\n\n const diffDays = Math.round(diffHours / 24)\n return formatter.format(diffDays, 'day')\n}\n\nexport function ChangelogEntryRow({ entry }: ChangelogEntryRowProps) {\n const t = useT()\n const ActionIcon = ACTION_ICONS[entry.actionType]\n const createdAt = new Date(entry.createdAt)\n const timeLabel = Number.isNaN(createdAt.getTime())\n ? '00:00'\n : createdAt.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n const relativeLabel = formatRelativeTime(entry.createdAt)\n const actorLabel = entry.actorUserName ?? t('customers.changelog.user.system', 'System')\n const actionLabel = t(`customers.changelog.actions.${entry.actionType}`, formatFieldLabel(entry.actionType))\n const sourceLabel = entry.source === 'api'\n ? t('customers.changelog.source.api', 'API')\n : entry.source === 'system'\n ? t('customers.changelog.source.system', 'System')\n : t('customers.changelog.source.ui', 'UI')\n\n return (\n <div className=\"grid grid-cols-[92px_190px_120px_1fr_80px] gap-3 px-5 py-3 text-sm\">\n <div className=\"space-y-0.5\">\n <div className=\"font-medium text-foreground\">{timeLabel}</div>\n <div className=\"text-xs text-muted-foreground\">{relativeLabel}</div>\n </div>\n\n <div className=\"flex items-start gap-2\">\n <div className=\"flex size-7 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold text-muted-foreground\">\n {entry.actorUserId ? getInitials(actorLabel) : <Settings2 className=\"size-3.5\" />}\n </div>\n <span className=\"truncate pt-1 text-sm text-foreground\">{actorLabel}</span>\n </div>\n\n <div className=\"flex items-start gap-2 pt-0.5 text-sm text-foreground\">\n <ActionIcon className=\"mt-0.5 size-4 text-muted-foreground\" />\n <span>{actionLabel}</span>\n </div>\n\n <div className=\"min-w-0 space-y-1.5\">\n {entry.changes.length > 0 ? (\n entry.changes.map((change) => (\n <div key={`${entry.id}-${change.fieldName}`} className=\"flex flex-wrap items-center gap-2 text-sm\">\n <span className=\"font-medium text-foreground\">{formatFieldLabel(change.fieldName)}</span>\n <Badge\n variant=\"outline\"\n className=\"max-w-[12rem] rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-medium text-status-error-text\"\n >\n <span className=\"truncate\">{change.oldValue || '\u2014'}</span>\n </Badge>\n <ArrowRight className=\"size-3.5 text-muted-foreground\" />\n <Badge\n variant=\"outline\"\n className=\"max-w-[12rem] rounded-full border-status-success-border bg-status-success-bg px-2 py-0.5 text-xs font-medium text-status-success-text\"\n >\n <span className=\"truncate\">{change.newValue || '\u2014'}</span>\n </Badge>\n </div>\n ))\n ) : (\n <span className=\"text-sm text-muted-foreground\">{entry.description ?? actionLabel}</span>\n )}\n </div>\n\n <div className=\"flex items-start justify-end\">\n <Badge variant=\"secondary\" className=\"px-1.5 py-0 text-xs\">\n {sourceLabel}\n </Badge>\n </div>\n </div>\n )\n}\n\nexport default ChangelogEntryRow\n"],
5
- "mappings": ";AAgFM,SACE,KADF;AA7EN,SAAS,aAAa;AACtB,SAAS,WAAW,WAAW,MAAM,QAAQ,eAAe,kBAAkB;AAC9E,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAsB5B,MAAM,eAAyF;AAAA,EAC7F,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AACV;AAEA,SAAS,iBAAiB,WAA2B;AACnD,SAAO,UACJ,QAAQ,OAAO,GAAG,EAClB,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,UAAU,GAAG,EACrB,QAAQ,QAAQ,GAAG,EACnB,KAAK,EACL,QAAQ,OAAO,CAAC,SAAS,KAAK,YAAY,CAAC;AAChD;AAEA,SAAS,mBAAmB,OAAuB;AACjD,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO;AACzC,QAAM,SAAS,KAAK,QAAQ,IAAI,KAAK,IAAI;AACzC,QAAM,cAAc,KAAK,MAAM,SAAS,GAAK;AAC7C,QAAM,YAAY,IAAI,KAAK,mBAAmB,QAAW,EAAE,SAAS,OAAO,CAAC;AAE5E,MAAI,KAAK,IAAI,WAAW,IAAI,GAAI,QAAO,UAAU,OAAO,aAAa,QAAQ;AAE7E,QAAM,YAAY,KAAK,MAAM,cAAc,EAAE;AAC7C,MAAI,KAAK,IAAI,SAAS,IAAI,GAAI,QAAO,UAAU,OAAO,WAAW,MAAM;AAEvE,QAAM,WAAW,KAAK,MAAM,YAAY,EAAE;AAC1C,SAAO,UAAU,OAAO,UAAU,KAAK;AACzC;AAEO,SAAS,kBAAkB,EAAE,MAAM,GAA2B;AACnE,QAAM,IAAI,KAAK;AACf,QAAM,aAAa,aAAa,MAAM,UAAU;AAChD,QAAM,YAAY,IAAI,KAAK,MAAM,SAAS;AAC1C,QAAM,YAAY,OAAO,MAAM,UAAU,QAAQ,CAAC,IAC9C,UACA,UAAU,mBAAmB,QAAW,EAAE,MAAM,WAAW,QAAQ,UAAU,CAAC;AAClF,QAAM,gBAAgB,mBAAmB,MAAM,SAAS;AACxD,QAAM,aAAa,MAAM,iBAAiB,EAAE,mCAAmC,QAAQ;AACvF,QAAM,cAAc,EAAE,+BAA+B,MAAM,UAAU,IAAI,iBAAiB,MAAM,UAAU,CAAC;AAC3G,QAAM,cAAc,MAAM,WAAW,QACjC,EAAE,kCAAkC,KAAK,IACzC,MAAM,WAAW,WACf,EAAE,qCAAqC,QAAQ,IAC/C,EAAE,iCAAiC,IAAI;AAE7C,SACE,qBAAC,SAAI,WAAU,sEACb;AAAA,yBAAC,SAAI,WAAU,eACb;AAAA,0BAAC,SAAI,WAAU,+BAA+B,qBAAU;AAAA,MACxD,oBAAC,SAAI,WAAU,iCAAiC,yBAAc;AAAA,OAChE;AAAA,IAEA,qBAAC,SAAI,WAAU,0BACb;AAAA,0BAAC,SAAI,WAAU,sHACZ,gBAAM,cAAc,YAAY,UAAU,IAAI,oBAAC,aAAU,WAAU,YAAW,GACjF;AAAA,MACA,oBAAC,UAAK,WAAU,yCAAyC,sBAAW;AAAA,OACtE;AAAA,IAEA,qBAAC,SAAI,WAAU,yDACb;AAAA,0BAAC,cAAW,WAAU,uCAAsC;AAAA,MAC5D,oBAAC,UAAM,uBAAY;AAAA,OACrB;AAAA,IAEA,oBAAC,SAAI,WAAU,uBACZ,gBAAM,QAAQ,SAAS,IACtB,MAAM,QAAQ,IAAI,CAAC,WACjB,qBAAC,SAA4C,WAAU,6CACrD;AAAA,0BAAC,UAAK,WAAU,+BAA+B,2BAAiB,OAAO,SAAS,GAAE;AAAA,MAClF;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,WAAU;AAAA,UAEV,8BAAC,UAAK,WAAU,YAAY,iBAAO,YAAY,UAAI;AAAA;AAAA,MACrD;AAAA,MACA,oBAAC,cAAW,WAAU,kCAAiC;AAAA,MACvD;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,WAAU;AAAA,UAEV,8BAAC,UAAK,WAAU,YAAY,iBAAO,YAAY,UAAI;AAAA;AAAA,MACrD;AAAA,SAdQ,GAAG,MAAM,EAAE,IAAI,OAAO,SAAS,EAezC,CACD,IAED,oBAAC,UAAK,WAAU,iCAAiC,gBAAM,eAAe,aAAY,GAEtF;AAAA,IAEA,oBAAC,SAAI,WAAU,gCACb,8BAAC,SAAM,SAAQ,aAAY,WAAU,uBAClC,uBACH,GACF;AAAA,KACF;AAEJ;AAEA,IAAO,4BAAQ;",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Settings2, SquarePen, Plus, Trash2, UserRoundPlus, ArrowRight } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype ChangelogActionType = 'create' | 'edit' | 'delete' | 'assign' | 'system'\ntype ChangelogSource = 'ui' | 'api' | 'system'\n\ntype ChangelogEntryRowProps = {\n entry: {\n id: string\n createdAt: string\n actorUserId: string | null\n actorUserName: string | null\n actionType: ChangelogActionType\n source: ChangelogSource\n description: string | null\n changes: Array<{\n fieldName: string\n oldValue: string\n newValue: string\n }>\n }\n}\n\nconst ACTION_ICONS: Record<ChangelogActionType, React.ComponentType<{ className?: string }>> = {\n create: Plus,\n edit: SquarePen,\n delete: Trash2,\n assign: UserRoundPlus,\n system: Settings2,\n}\n\nfunction formatFieldLabel(fieldName: string): string {\n return fieldName\n .replace(/\\./g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^\\w/, (char) => char.toUpperCase())\n}\n\nfunction formatRelativeTime(value: string): string {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n const diffMs = date.getTime() - Date.now()\n const diffMinutes = Math.round(diffMs / 60000)\n const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })\n\n if (Math.abs(diffMinutes) < 60) return formatter.format(diffMinutes, 'minute')\n\n const diffHours = Math.round(diffMinutes / 60)\n if (Math.abs(diffHours) < 24) return formatter.format(diffHours, 'hour')\n\n const diffDays = Math.round(diffHours / 24)\n return formatter.format(diffDays, 'day')\n}\n\nexport function ChangelogEntryRow({ entry }: ChangelogEntryRowProps) {\n const t = useT()\n const ActionIcon = ACTION_ICONS[entry.actionType]\n const createdAt = new Date(entry.createdAt)\n const timeLabel = Number.isNaN(createdAt.getTime())\n ? '00:00'\n : createdAt.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n const relativeLabel = formatRelativeTime(entry.createdAt)\n const actorLabel = entry.actorUserName ?? t('customers.changelog.user.system', 'System')\n const actionLabel = t(`customers.changelog.actions.${entry.actionType}`, formatFieldLabel(entry.actionType))\n const sourceLabel = entry.source === 'api'\n ? t('customers.changelog.source.api', 'API')\n : entry.source === 'system'\n ? t('customers.changelog.source.system', 'System')\n : t('customers.changelog.source.ui', 'UI')\n\n return (\n <div className=\"grid grid-cols-[92px_190px_120px_1fr_80px] gap-3 px-5 py-3 text-sm\">\n <div className=\"space-y-0.5\">\n <div className=\"font-medium text-foreground\">{timeLabel}</div>\n <div className=\"text-xs text-muted-foreground\">{relativeLabel}</div>\n </div>\n\n <div className=\"flex items-start gap-2\">\n {entry.actorUserId ? (\n <Avatar label={actorLabel} size=\"sm\" variant=\"monochrome\" />\n ) : (\n <Avatar label=\"\" icon={<Settings2 />} size=\"sm\" variant=\"monochrome\" />\n )}\n <span className=\"truncate pt-1 text-sm text-foreground\">{actorLabel}</span>\n </div>\n\n <div className=\"flex items-start gap-2 pt-0.5 text-sm text-foreground\">\n <ActionIcon className=\"mt-0.5 size-4 text-muted-foreground\" />\n <span>{actionLabel}</span>\n </div>\n\n <div className=\"min-w-0 space-y-1.5\">\n {entry.changes.length > 0 ? (\n entry.changes.map((change) => (\n <div key={`${entry.id}-${change.fieldName}`} className=\"flex flex-wrap items-center gap-2 text-sm\">\n <span className=\"font-medium text-foreground\">{formatFieldLabel(change.fieldName)}</span>\n <Badge\n variant=\"outline\"\n className=\"max-w-[12rem] rounded-full border-status-error-border bg-status-error-bg px-2 py-0.5 text-xs font-medium text-status-error-text\"\n >\n <span className=\"truncate\">{change.oldValue || '\u2014'}</span>\n </Badge>\n <ArrowRight className=\"size-3.5 text-muted-foreground\" />\n <Badge\n variant=\"outline\"\n className=\"max-w-[12rem] rounded-full border-status-success-border bg-status-success-bg px-2 py-0.5 text-xs font-medium text-status-success-text\"\n >\n <span className=\"truncate\">{change.newValue || '\u2014'}</span>\n </Badge>\n </div>\n ))\n ) : (\n <span className=\"text-sm text-muted-foreground\">{entry.description ?? actionLabel}</span>\n )}\n </div>\n\n <div className=\"flex items-start justify-end\">\n <Badge variant=\"secondary\" className=\"px-1.5 py-0 text-xs\">\n {sourceLabel}\n </Badge>\n </div>\n </div>\n )\n}\n\nexport default ChangelogEntryRow\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { Phone, Mail, Trash2, Building2, Globe, Pencil, MapPin } from "lucide-react";
5
5
  import { useQueryClient } from "@tanstack/react-query";
6
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
7
+ import { Avatar } from "@open-mercato/ui/primitives/avatar";
7
8
  import { Button } from "@open-mercato/ui/primitives/button";
8
9
  import { IconButton } from "@open-mercato/ui/primitives/icon-button";
9
10
  import { Badge } from "@open-mercato/ui/primitives/badge";
@@ -70,7 +71,15 @@ function CompanyDetailHeader({
70
71
  const { data: renewalQuarterDict } = useCustomerDictionary("renewal-quarters", 0, companyOrgId);
71
72
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card", children: [
72
73
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 px-6 pt-6 pb-3 sm:flex-row sm:items-start sm:gap-5", children: [
73
- /* @__PURE__ */ jsx("div", { className: "flex size-18 shrink-0 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx(Building2, { className: "size-7 text-muted-foreground" }) }),
74
+ /* @__PURE__ */ jsx(
75
+ Avatar,
76
+ {
77
+ label: "",
78
+ icon: /* @__PURE__ */ jsx(Building2, {}),
79
+ size: "xl",
80
+ variant: "monochrome"
81
+ }
82
+ ),
74
83
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
75
84
  /* @__PURE__ */ jsx("h1", { className: "truncate text-2xl font-bold text-foreground", children: displayName }),
76
85
  subtitle && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: subtitle }),
@@ -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 { 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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",
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 { Avatar } from '@open-mercato/ui/primitives/avatar'\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 <Avatar\n label=\"\"\n icon={<Building2 />}\n size=\"xl\"\n variant=\"monochrome\"\n />\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
  }
@@ -2,10 +2,11 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import Link from "next/link";
5
- import { ArrowLeft, ArrowRight, Link2 } from "lucide-react";
5
+ import { Link2 } from "lucide-react";
6
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
7
7
  import { Button } from "@open-mercato/ui/primitives/button";
8
8
  import { Input } from "@open-mercato/ui/primitives/input";
9
+ import { Pagination } from "@open-mercato/ui/primitives/pagination";
9
10
  import { LinkEntityDialog } from "../linking/LinkEntityDialog.js";
10
11
  const PAGE_SIZE = 20;
11
12
  function normalizeText(value) {
@@ -20,53 +21,6 @@ function applyFilter(items, query) {
20
21
  return label.includes(normalizedQuery) || subtitle.includes(normalizedQuery);
21
22
  });
22
23
  }
23
- function Pagination({
24
- page,
25
- totalPages,
26
- onPageChange
27
- }) {
28
- if (totalPages <= 1) return null;
29
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border/70 pt-3 text-sm text-muted-foreground", children: [
30
- /* @__PURE__ */ jsxs("span", { children: [
31
- "Page ",
32
- page,
33
- " of ",
34
- totalPages
35
- ] }),
36
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
37
- /* @__PURE__ */ jsxs(
38
- Button,
39
- {
40
- type: "button",
41
- variant: "outline",
42
- size: "sm",
43
- className: "h-8 rounded-lg px-3 text-xs",
44
- onClick: () => onPageChange(Math.max(1, page - 1)),
45
- disabled: page <= 1,
46
- children: [
47
- /* @__PURE__ */ jsx(ArrowLeft, { className: "mr-1.5 size-3.5" }),
48
- "Previous"
49
- ]
50
- }
51
- ),
52
- /* @__PURE__ */ jsxs(
53
- Button,
54
- {
55
- type: "button",
56
- variant: "outline",
57
- size: "sm",
58
- className: "h-8 rounded-lg px-3 text-xs",
59
- onClick: () => onPageChange(Math.min(totalPages, page + 1)),
60
- disabled: page >= totalPages,
61
- children: [
62
- "Next",
63
- /* @__PURE__ */ jsx(ArrowRight, { className: "ml-1.5 size-3.5" })
64
- ]
65
- }
66
- )
67
- ] })
68
- ] });
69
- }
70
24
  function DealLinkedEntitiesTab({
71
25
  entityLabel,
72
26
  entityLabelPlural,
@@ -262,14 +216,16 @@ function DealLinkedEntitiesTab({
262
216
  },
263
217
  item.id
264
218
  )),
265
- /* @__PURE__ */ jsx(
219
+ visibleTotalPages > 1 ? /* @__PURE__ */ jsx(
266
220
  Pagination,
267
221
  {
222
+ className: "border-t border-border/70 pt-3",
268
223
  page: visiblePage,
269
- totalPages: visibleTotalPages,
224
+ pageSize: PAGE_SIZE,
225
+ total: visibleTotalPages * PAGE_SIZE,
270
226
  onPageChange: setPage
271
227
  }
272
- )
228
+ ) : null
273
229
  ] }) : remoteLinkedLoading ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-border bg-muted/20 px-5 py-5 text-sm text-muted-foreground", children: t("customers.deals.detail.linkedEntities.loading", "Loading linked records\u2026") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-border bg-muted/20 px-5 py-5 text-sm text-muted-foreground", children: pageSearch.trim().length ? t(
274
230
  "customers.deals.detail.linkedEntities.noSearchMatches",
275
231
  "No linked records match the current search."
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/DealLinkedEntitiesTab.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { ArrowLeft, ArrowRight, Link2 } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { LinkEntityDialog, type LinkEntityAdapter, type LinkEntityOption } from '../linking/LinkEntityDialog'\n\ntype LinkedEntityOption = {\n id: string\n label: string\n subtitle?: string | null\n}\n\ntype DealLinkedEntitiesTabProps = {\n entityLabel: string\n entityLabelPlural: string\n manageLabel: string\n searchPlaceholder: string\n linkedItems: LinkedEntityOption[]\n linkedCount?: number\n selectedIds: string[]\n disabled?: boolean\n savePending?: boolean\n hrefBuilder: (id: string) => string\n onSaveSelection: (nextIds: string[]) => Promise<void> | void\n loadLinkedPage?: (\n page: number,\n query: string,\n ) => Promise<{ items: LinkedEntityOption[]; totalPages: number; total: number }>\n searchEntities: (\n query: string,\n page: number,\n ) => Promise<{ items: LinkedEntityOption[]; totalPages: number }>\n fetchEntitiesByIds: (ids: string[]) => Promise<LinkedEntityOption[]>\n icon: React.ReactNode\n}\n\nconst PAGE_SIZE = 20\n\nfunction normalizeText(value: string): string {\n return value.trim().toLowerCase()\n}\n\nfunction applyFilter(items: LinkedEntityOption[], query: string): LinkedEntityOption[] {\n const normalizedQuery = normalizeText(query)\n if (!normalizedQuery.length) return items\n return items.filter((item) => {\n const label = normalizeText(item.label)\n const subtitle = normalizeText(item.subtitle ?? '')\n return label.includes(normalizedQuery) || subtitle.includes(normalizedQuery)\n })\n}\n\nfunction Pagination({\n page,\n totalPages,\n onPageChange,\n}: {\n page: number\n totalPages: number\n onPageChange: (page: number) => void\n}) {\n if (totalPages <= 1) return null\n return (\n <div className=\"flex items-center justify-between border-t border-border/70 pt-3 text-sm text-muted-foreground\">\n <span>\n Page {page} of {totalPages}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 rounded-lg px-3 text-xs\"\n onClick={() => onPageChange(Math.max(1, page - 1))}\n disabled={page <= 1}\n >\n <ArrowLeft className=\"mr-1.5 size-3.5\" />\n Previous\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 rounded-lg px-3 text-xs\"\n onClick={() => onPageChange(Math.min(totalPages, page + 1))}\n disabled={page >= totalPages}\n >\n Next\n <ArrowRight className=\"ml-1.5 size-3.5\" />\n </Button>\n </div>\n </div>\n )\n}\n\nexport function DealLinkedEntitiesTab({\n entityLabel,\n entityLabelPlural,\n manageLabel,\n searchPlaceholder,\n linkedItems,\n linkedCount,\n selectedIds,\n disabled = false,\n savePending = false,\n hrefBuilder,\n onSaveSelection,\n loadLinkedPage,\n searchEntities,\n fetchEntitiesByIds,\n icon,\n}: DealLinkedEntitiesTabProps) {\n const t = useT()\n const useRemoteLinkedList = typeof loadLinkedPage === 'function'\n const [pageSearch, setPageSearch] = React.useState('')\n const [page, setPage] = React.useState(1)\n const [remoteLinkedItems, setRemoteLinkedItems] = React.useState<LinkedEntityOption[]>([])\n const [remoteLinkedTotalPages, setRemoteLinkedTotalPages] = React.useState(1)\n const [remoteLinkedLoading, setRemoteLinkedLoading] = React.useState(false)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const selectedIdsKey = React.useMemo(() => selectedIds.join('|'), [selectedIds])\n\n const refreshRemoteLinkedList = React.useCallback(\n async (options?: { showLoading?: boolean }) => {\n if (!useRemoteLinkedList || !loadLinkedPage) return\n const showLoading = options?.showLoading ?? true\n if (showLoading) setRemoteLinkedLoading(true)\n try {\n const result = await loadLinkedPage(page, pageSearch)\n setRemoteLinkedItems(result.items)\n setRemoteLinkedTotalPages(result.totalPages)\n } catch {\n setRemoteLinkedItems([])\n setRemoteLinkedTotalPages(1)\n } finally {\n if (showLoading) setRemoteLinkedLoading(false)\n }\n },\n [loadLinkedPage, page, pageSearch, useRemoteLinkedList],\n )\n\n React.useEffect(() => {\n if (!useRemoteLinkedList || !loadLinkedPage) return\n void refreshRemoteLinkedList()\n }, [loadLinkedPage, refreshRemoteLinkedList, useRemoteLinkedList])\n\n React.useEffect(() => {\n if (!useRemoteLinkedList || dialogOpen) return\n void refreshRemoteLinkedList({ showLoading: false })\n }, [dialogOpen, refreshRemoteLinkedList, selectedIdsKey, useRemoteLinkedList])\n\n React.useEffect(() => {\n setPage(1)\n }, [pageSearch])\n\n const filteredLinkedItems = React.useMemo(\n () => applyFilter(linkedItems, pageSearch),\n [linkedItems, pageSearch],\n )\n const totalPages = Math.max(1, Math.ceil(filteredLinkedItems.length / PAGE_SIZE))\n const currentPage = Math.min(page, totalPages)\n const pagedLinkedItems = React.useMemo(\n () =>\n filteredLinkedItems.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE),\n [currentPage, filteredLinkedItems],\n )\n const visibleLinkedItems = useRemoteLinkedList ? remoteLinkedItems : pagedLinkedItems\n const visiblePage = useRemoteLinkedList ? page : currentPage\n const visibleTotalPages = useRemoteLinkedList ? remoteLinkedTotalPages : totalPages\n const totalLinkedCount = linkedCount ?? linkedItems.length\n\n const adapter = React.useMemo<LinkEntityAdapter>(\n () => ({\n kind: 'person',\n dialogTitle: t(\n 'customers.deals.detail.linkedEntities.dialogTitleShort',\n 'Link {{entity}}',\n { entity: entityLabel.toLowerCase() },\n ),\n dialogSubtitle: t(\n 'customers.deals.detail.linkedEntities.dialogSubtitle',\n 'Link an existing {{entity}} to this deal',\n { entity: entityLabel.toLowerCase() },\n ),\n sectionLabel: t(\n 'customers.deals.detail.linkedEntities.sectionLabel',\n 'MATCHING {{entity}}',\n { entity: entityLabelPlural.toUpperCase() },\n ),\n searchPlaceholder: t(\n 'customers.deals.detail.linkedEntities.searchAll',\n 'Search all {{entity}}\u2026',\n { entity: entityLabelPlural.toLowerCase() },\n ),\n searchEmptyHint: t(\n 'customers.deals.detail.linkedEntities.noResults',\n 'No matching records found.',\n ),\n selectedEmptyHint: t(\n 'customers.deals.detail.linkedEntities.noneSelected',\n 'No linked records selected.',\n ),\n confirmButtonLabel: t(\n 'customers.deals.detail.linkedEntities.confirmButton',\n 'Link {{entity}}',\n { entity: entityLabel.toLowerCase() },\n ),\n defaultAvatarIcon: icon,\n searchPage: async (query, searchPageIndex) => {\n const result = await searchEntities(query, searchPageIndex)\n return {\n items: result.items.map((item) => ({\n id: item.id,\n label: item.label,\n subtitle: item.subtitle ?? null,\n })),\n totalPages: result.totalPages,\n }\n },\n fetchByIds: async (ids) => {\n const result = await fetchEntitiesByIds(ids)\n return result.map((item) => ({\n id: item.id,\n label: item.label,\n subtitle: item.subtitle ?? null,\n }))\n },\n }),\n [entityLabel, entityLabelPlural, fetchEntitiesByIds, icon, searchEntities, t],\n )\n\n const handleDialogConfirm = React.useCallback(\n async ({\n nextSelectedIds,\n }: {\n addedIds: string[]\n removedIds: string[]\n nextSelectedIds: string[]\n optionsById: Record<string, LinkEntityOption>\n }) => {\n await onSaveSelection(nextSelectedIds)\n if (useRemoteLinkedList) {\n await refreshRemoteLinkedList({ showLoading: false })\n }\n },\n [onSaveSelection, refreshRemoteLinkedList, useRemoteLinkedList],\n )\n\n return (\n <>\n <div className=\"space-y-5\">\n <div className=\"flex flex-col gap-3 rounded-xl border border-border/70 bg-muted/10 p-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-semibold text-foreground\">{manageLabel}</div>\n <div className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.deals.detail.linkedEntities.summary',\n '{{count}} linked {{entity}}',\n {\n count: totalLinkedCount,\n entity:\n totalLinkedCount === 1\n ? entityLabel.toLowerCase()\n : entityLabelPlural.toLowerCase(),\n },\n )}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"relative min-w-0 flex-1 sm:w-[260px]\">\n <Input\n value={pageSearch}\n onChange={(event) => setPageSearch(event.target.value)}\n placeholder={searchPlaceholder}\n />\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-9 rounded-lg px-3\"\n onClick={() => setDialogOpen(true)}\n disabled={disabled || savePending}\n >\n <Link2 className=\"mr-2 size-4\" />\n {t('customers.deals.detail.linkedEntities.manage', 'Manage links')}\n </Button>\n </div>\n </div>\n\n {visibleLinkedItems.length ? (\n <div className=\"space-y-3\">\n {visibleLinkedItems.map((item) => (\n <Link\n key={item.id}\n href={hrefBuilder(item.id)}\n className=\"flex items-start gap-3 rounded-xl border border-border/70 bg-card px-4 py-4 transition-colors hover:bg-accent\"\n >\n <div className=\"mt-0.5 rounded-full bg-muted p-2 text-muted-foreground\">\n {icon}\n </div>\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-semibold text-foreground\">\n {item.label}\n </div>\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {item.subtitle || '\u2014'}\n </div>\n </div>\n </Link>\n ))}\n <Pagination\n page={visiblePage}\n totalPages={visibleTotalPages}\n onPageChange={setPage}\n />\n </div>\n ) : remoteLinkedLoading ? (\n <div className=\"rounded-lg border border-border bg-muted/20 px-5 py-5 text-sm text-muted-foreground\">\n {t('customers.deals.detail.linkedEntities.loading', 'Loading linked records\u2026')}\n </div>\n ) : (\n <div className=\"rounded-lg border border-border bg-muted/20 px-5 py-5 text-sm text-muted-foreground\">\n {pageSearch.trim().length\n ? t(\n 'customers.deals.detail.linkedEntities.noSearchMatches',\n 'No linked records match the current search.',\n )\n : t('customers.deals.detail.linkedEntities.empty', 'No linked records yet.')}\n </div>\n )}\n </div>\n\n <LinkEntityDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n adapter={adapter}\n initialSelectedIds={selectedIds}\n onConfirm={handleDialogConfirm}\n />\n </>\n )\n}\n\nexport default DealLinkedEntitiesTab\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Link2 } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Pagination } from '@open-mercato/ui/primitives/pagination'\nimport { LinkEntityDialog, type LinkEntityAdapter, type LinkEntityOption } from '../linking/LinkEntityDialog'\n\ntype LinkedEntityOption = {\n id: string\n label: string\n subtitle?: string | null\n}\n\ntype DealLinkedEntitiesTabProps = {\n entityLabel: string\n entityLabelPlural: string\n manageLabel: string\n searchPlaceholder: string\n linkedItems: LinkedEntityOption[]\n linkedCount?: number\n selectedIds: string[]\n disabled?: boolean\n savePending?: boolean\n hrefBuilder: (id: string) => string\n onSaveSelection: (nextIds: string[]) => Promise<void> | void\n loadLinkedPage?: (\n page: number,\n query: string,\n ) => Promise<{ items: LinkedEntityOption[]; totalPages: number; total: number }>\n searchEntities: (\n query: string,\n page: number,\n ) => Promise<{ items: LinkedEntityOption[]; totalPages: number }>\n fetchEntitiesByIds: (ids: string[]) => Promise<LinkedEntityOption[]>\n icon: React.ReactNode\n}\n\nconst PAGE_SIZE = 20\n\nfunction normalizeText(value: string): string {\n return value.trim().toLowerCase()\n}\n\nfunction applyFilter(items: LinkedEntityOption[], query: string): LinkedEntityOption[] {\n const normalizedQuery = normalizeText(query)\n if (!normalizedQuery.length) return items\n return items.filter((item) => {\n const label = normalizeText(item.label)\n const subtitle = normalizeText(item.subtitle ?? '')\n return label.includes(normalizedQuery) || subtitle.includes(normalizedQuery)\n })\n}\n\nexport function DealLinkedEntitiesTab({\n entityLabel,\n entityLabelPlural,\n manageLabel,\n searchPlaceholder,\n linkedItems,\n linkedCount,\n selectedIds,\n disabled = false,\n savePending = false,\n hrefBuilder,\n onSaveSelection,\n loadLinkedPage,\n searchEntities,\n fetchEntitiesByIds,\n icon,\n}: DealLinkedEntitiesTabProps) {\n const t = useT()\n const useRemoteLinkedList = typeof loadLinkedPage === 'function'\n const [pageSearch, setPageSearch] = React.useState('')\n const [page, setPage] = React.useState(1)\n const [remoteLinkedItems, setRemoteLinkedItems] = React.useState<LinkedEntityOption[]>([])\n const [remoteLinkedTotalPages, setRemoteLinkedTotalPages] = React.useState(1)\n const [remoteLinkedLoading, setRemoteLinkedLoading] = React.useState(false)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const selectedIdsKey = React.useMemo(() => selectedIds.join('|'), [selectedIds])\n\n const refreshRemoteLinkedList = React.useCallback(\n async (options?: { showLoading?: boolean }) => {\n if (!useRemoteLinkedList || !loadLinkedPage) return\n const showLoading = options?.showLoading ?? true\n if (showLoading) setRemoteLinkedLoading(true)\n try {\n const result = await loadLinkedPage(page, pageSearch)\n setRemoteLinkedItems(result.items)\n setRemoteLinkedTotalPages(result.totalPages)\n } catch {\n setRemoteLinkedItems([])\n setRemoteLinkedTotalPages(1)\n } finally {\n if (showLoading) setRemoteLinkedLoading(false)\n }\n },\n [loadLinkedPage, page, pageSearch, useRemoteLinkedList],\n )\n\n React.useEffect(() => {\n if (!useRemoteLinkedList || !loadLinkedPage) return\n void refreshRemoteLinkedList()\n }, [loadLinkedPage, refreshRemoteLinkedList, useRemoteLinkedList])\n\n React.useEffect(() => {\n if (!useRemoteLinkedList || dialogOpen) return\n void refreshRemoteLinkedList({ showLoading: false })\n }, [dialogOpen, refreshRemoteLinkedList, selectedIdsKey, useRemoteLinkedList])\n\n React.useEffect(() => {\n setPage(1)\n }, [pageSearch])\n\n const filteredLinkedItems = React.useMemo(\n () => applyFilter(linkedItems, pageSearch),\n [linkedItems, pageSearch],\n )\n const totalPages = Math.max(1, Math.ceil(filteredLinkedItems.length / PAGE_SIZE))\n const currentPage = Math.min(page, totalPages)\n const pagedLinkedItems = React.useMemo(\n () =>\n filteredLinkedItems.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE),\n [currentPage, filteredLinkedItems],\n )\n const visibleLinkedItems = useRemoteLinkedList ? remoteLinkedItems : pagedLinkedItems\n const visiblePage = useRemoteLinkedList ? page : currentPage\n const visibleTotalPages = useRemoteLinkedList ? remoteLinkedTotalPages : totalPages\n const totalLinkedCount = linkedCount ?? linkedItems.length\n\n const adapter = React.useMemo<LinkEntityAdapter>(\n () => ({\n kind: 'person',\n dialogTitle: t(\n 'customers.deals.detail.linkedEntities.dialogTitleShort',\n 'Link {{entity}}',\n { entity: entityLabel.toLowerCase() },\n ),\n dialogSubtitle: t(\n 'customers.deals.detail.linkedEntities.dialogSubtitle',\n 'Link an existing {{entity}} to this deal',\n { entity: entityLabel.toLowerCase() },\n ),\n sectionLabel: t(\n 'customers.deals.detail.linkedEntities.sectionLabel',\n 'MATCHING {{entity}}',\n { entity: entityLabelPlural.toUpperCase() },\n ),\n searchPlaceholder: t(\n 'customers.deals.detail.linkedEntities.searchAll',\n 'Search all {{entity}}\u2026',\n { entity: entityLabelPlural.toLowerCase() },\n ),\n searchEmptyHint: t(\n 'customers.deals.detail.linkedEntities.noResults',\n 'No matching records found.',\n ),\n selectedEmptyHint: t(\n 'customers.deals.detail.linkedEntities.noneSelected',\n 'No linked records selected.',\n ),\n confirmButtonLabel: t(\n 'customers.deals.detail.linkedEntities.confirmButton',\n 'Link {{entity}}',\n { entity: entityLabel.toLowerCase() },\n ),\n defaultAvatarIcon: icon,\n searchPage: async (query, searchPageIndex) => {\n const result = await searchEntities(query, searchPageIndex)\n return {\n items: result.items.map((item) => ({\n id: item.id,\n label: item.label,\n subtitle: item.subtitle ?? null,\n })),\n totalPages: result.totalPages,\n }\n },\n fetchByIds: async (ids) => {\n const result = await fetchEntitiesByIds(ids)\n return result.map((item) => ({\n id: item.id,\n label: item.label,\n subtitle: item.subtitle ?? null,\n }))\n },\n }),\n [entityLabel, entityLabelPlural, fetchEntitiesByIds, icon, searchEntities, t],\n )\n\n const handleDialogConfirm = React.useCallback(\n async ({\n nextSelectedIds,\n }: {\n addedIds: string[]\n removedIds: string[]\n nextSelectedIds: string[]\n optionsById: Record<string, LinkEntityOption>\n }) => {\n await onSaveSelection(nextSelectedIds)\n if (useRemoteLinkedList) {\n await refreshRemoteLinkedList({ showLoading: false })\n }\n },\n [onSaveSelection, refreshRemoteLinkedList, useRemoteLinkedList],\n )\n\n return (\n <>\n <div className=\"space-y-5\">\n <div className=\"flex flex-col gap-3 rounded-xl border border-border/70 bg-muted/10 p-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-semibold text-foreground\">{manageLabel}</div>\n <div className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.deals.detail.linkedEntities.summary',\n '{{count}} linked {{entity}}',\n {\n count: totalLinkedCount,\n entity:\n totalLinkedCount === 1\n ? entityLabel.toLowerCase()\n : entityLabelPlural.toLowerCase(),\n },\n )}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"relative min-w-0 flex-1 sm:w-[260px]\">\n <Input\n value={pageSearch}\n onChange={(event) => setPageSearch(event.target.value)}\n placeholder={searchPlaceholder}\n />\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-9 rounded-lg px-3\"\n onClick={() => setDialogOpen(true)}\n disabled={disabled || savePending}\n >\n <Link2 className=\"mr-2 size-4\" />\n {t('customers.deals.detail.linkedEntities.manage', 'Manage links')}\n </Button>\n </div>\n </div>\n\n {visibleLinkedItems.length ? (\n <div className=\"space-y-3\">\n {visibleLinkedItems.map((item) => (\n <Link\n key={item.id}\n href={hrefBuilder(item.id)}\n className=\"flex items-start gap-3 rounded-xl border border-border/70 bg-card px-4 py-4 transition-colors hover:bg-accent\"\n >\n <div className=\"mt-0.5 rounded-full bg-muted p-2 text-muted-foreground\">\n {icon}\n </div>\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-semibold text-foreground\">\n {item.label}\n </div>\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {item.subtitle || '\u2014'}\n </div>\n </div>\n </Link>\n ))}\n {visibleTotalPages > 1 ? (\n <Pagination\n className=\"border-t border-border/70 pt-3\"\n page={visiblePage}\n pageSize={PAGE_SIZE}\n total={visibleTotalPages * PAGE_SIZE}\n onPageChange={setPage}\n />\n ) : null}\n </div>\n ) : remoteLinkedLoading ? (\n <div className=\"rounded-lg border border-border bg-muted/20 px-5 py-5 text-sm text-muted-foreground\">\n {t('customers.deals.detail.linkedEntities.loading', 'Loading linked records\u2026')}\n </div>\n ) : (\n <div className=\"rounded-lg border border-border bg-muted/20 px-5 py-5 text-sm text-muted-foreground\">\n {pageSearch.trim().length\n ? t(\n 'customers.deals.detail.linkedEntities.noSearchMatches',\n 'No linked records match the current search.',\n )\n : t('customers.deals.detail.linkedEntities.empty', 'No linked records yet.')}\n </div>\n )}\n </div>\n\n <LinkEntityDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n adapter={adapter}\n initialSelectedIds={selectedIds}\n onConfirm={handleDialogConfirm}\n />\n </>\n )\n}\n\nexport default DealLinkedEntitiesTab\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -41,7 +41,7 @@ function DetailTabsLayout({
41
41
  onClick: () => handleTabChange(tab.id),
42
42
  className: cn(
43
43
  "h-auto rounded-none border-b-2 px-0 py-1",
44
- activeTab === tab.id ? "border-primary text-foreground hover:bg-transparent" : "border-transparent text-muted-foreground hover:text-foreground hover:bg-transparent"
44
+ activeTab === tab.id ? "border-accent-indigo text-foreground hover:bg-transparent" : "border-transparent text-muted-foreground hover:text-foreground hover:bg-transparent"
45
45
  ),
46
46
  children: tab.label
47
47
  },