@open-mercato/core 0.6.4-develop.4199.1.86677441c2 → 0.6.4-develop.4217.1.c9aa050183

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
@@ -45,6 +45,7 @@ import { ChatPaneTabs } from '@open-mercato/ui/ai/ChatPaneTabs'
45
45
  import '../../../components/CatalogStatsCard'
46
46
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
47
47
  import { Button } from '@open-mercato/ui/primitives/button'
48
+ import { ButtonGroup } from '@open-mercato/ui/primitives/button-group'
48
49
  import { IconButton } from '@open-mercato/ui/primitives/icon-button'
49
50
  import {
50
51
  Dialog,
@@ -387,7 +388,7 @@ export function MerchandisingAssistantSheet({
387
388
 
388
389
  return (
389
390
  <>
390
- <div className={cn('inline-flex items-center', className)}>
391
+ <ButtonGroup className={className}>
391
392
  <Button
392
393
  type="button"
393
394
  variant="outline"
@@ -395,10 +396,7 @@ export function MerchandisingAssistantSheet({
395
396
  data-ai-merchandising-trigger=""
396
397
  aria-label={triggerLabel}
397
398
  title={triggerLabel}
398
- className={cn(
399
- 'relative',
400
- agents.length > 1 && 'rounded-r-none border-r-0',
401
- )}
399
+ className="relative"
402
400
  >
403
401
  <AiIcon className="size-4" />
404
402
  <span>{labelText}</span>
@@ -417,10 +415,8 @@ export function MerchandisingAssistantSheet({
417
415
  <IconButton
418
416
  type="button"
419
417
  variant="outline"
420
- size="lg"
421
418
  aria-label={moreAgentsLabel}
422
419
  title={moreAgentsLabel}
423
- className="rounded-l-none"
424
420
  data-ai-merchandising-picker=""
425
421
  >
426
422
  <ChevronDown className="size-4" aria-hidden />
@@ -454,7 +450,7 @@ export function MerchandisingAssistantSheet({
454
450
  </PopoverContent>
455
451
  </Popover>
456
452
  ) : null}
457
- </div>
453
+ </ButtonGroup>
458
454
  <Dialog open={open} onOpenChange={setOpen}>
459
455
  <DialogContent
460
456
  className={cn(
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import { Calendar, Check, ExternalLink, ListTodo, Mail, MoreHorizontal, Phone, StickyNote, Users } from 'lucide-react'
5
+ import { Avatar } from '@open-mercato/ui/primitives/avatar'
5
6
  import { Button } from '@open-mercato/ui/primitives/button'
6
7
  import { IconButton } from '@open-mercato/ui/primitives/icon-button'
7
8
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
@@ -10,7 +11,6 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
10
11
  import { cn } from '@open-mercato/shared/lib/utils'
11
12
  import type { InteractionSummary } from './types'
12
13
  import { ActivityAiActions } from './ActivityAiActions'
13
- import { getInitials } from './utils'
14
14
 
15
15
  type GuardedMutationRunner = <T,>(
16
16
  operation: () => Promise<T>,
@@ -199,9 +199,7 @@ export function ActivityCard({ activity, onOpen, onChanged, runMutation }: Activ
199
199
  ) : null}
200
200
 
201
201
  <div className="mt-3 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground">
202
- <span className="inline-flex size-5 items-center justify-center rounded-full bg-muted text-xs font-semibold text-foreground">
203
- {getInitials(actorLabel)}
204
- </span>
202
+ <Avatar label={actorLabel} size="xs" variant="monochrome" />
205
203
  <span className="font-medium text-foreground">{actorLabel}</span>
206
204
  {target && direction ? (
207
205
  <>
@@ -3,7 +3,9 @@
3
3
  import * as React from 'react'
4
4
  import Link from 'next/link'
5
5
  import { Search, Check, CheckCheck, Settings2 } from 'lucide-react'
6
+ import { Avatar } from '@open-mercato/ui/primitives/avatar'
6
7
  import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
8
+ import { StepIndicator, type StepIndicatorStep } from '@open-mercato/ui/primitives/step-indicator'
7
9
  import { useT } from '@open-mercato/shared/lib/i18n/context'
8
10
  import { Button } from '@open-mercato/ui/primitives/button'
9
11
  import { Badge } from '@open-mercato/ui/primitives/badge'
@@ -18,7 +20,6 @@ import {
18
20
  import type { DictionaryEntryOption } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'
19
21
  import type { RoleAssignment } from './RoleAssignmentRow'
20
22
  import { fetchAssignableStaffMembersPage } from './assignableStaff'
21
- import { getInitials } from './utils'
22
23
 
23
24
  const MANAGE_ROLE_TYPES_HREF = '/backend/config/customers'
24
25
 
@@ -297,9 +298,11 @@ export function AssignRoleDialog({
297
298
  {t('customers.roles.dialog.preview', 'Assignment preview')}
298
299
  </p>
299
300
  <div className="mt-3 flex items-center gap-3">
300
- <div className="flex size-12 items-center justify-center rounded-full bg-background text-sm font-semibold text-foreground">
301
- {getInitials(selectedUser.displayName)}
302
- </div>
301
+ <Avatar
302
+ label={selectedUser.displayName}
303
+ size="lg"
304
+ className="bg-background text-foreground"
305
+ />
303
306
  <div className="min-w-0">
304
307
  <div className="flex flex-wrap items-center gap-2 text-sm font-semibold text-foreground">
305
308
  <span>{selectedUser.displayName}</span>
@@ -350,25 +353,26 @@ export function AssignRoleDialog({
350
353
  </DialogHeader>
351
354
 
352
355
  <div className="border-b border-border/70 px-6 py-4">
353
- <div className="flex items-center justify-center gap-3 text-xs">
354
- <StepBadge
355
- step={1}
356
- currentStep={step}
357
- label={t('customers.roles.dialog.step1', 'Role type')}
358
- />
359
- <div className="h-px w-10 bg-border" />
360
- <StepBadge
361
- step={2}
362
- currentStep={step}
363
- label={t('customers.roles.dialog.step2', 'Select person')}
364
- />
365
- <div className="h-px w-10 bg-border" />
366
- <StepBadge
367
- step={3}
368
- currentStep={step}
369
- label={t('customers.roles.dialog.step3', 'Confirm')}
370
- />
371
- </div>
356
+ <StepIndicator
357
+ className="justify-center"
358
+ steps={[
359
+ {
360
+ id: '1',
361
+ label: t('customers.roles.dialog.step1', 'Role type'),
362
+ status: step > 1 ? 'complete' : step === 1 ? 'current' : 'pending',
363
+ },
364
+ {
365
+ id: '2',
366
+ label: t('customers.roles.dialog.step2', 'Select person'),
367
+ status: step > 2 ? 'complete' : step === 2 ? 'current' : 'pending',
368
+ },
369
+ {
370
+ id: '3',
371
+ label: t('customers.roles.dialog.step3', 'Confirm'),
372
+ status: step === 3 ? 'current' : 'pending',
373
+ },
374
+ ] satisfies StepIndicatorStep[]}
375
+ />
372
376
  </div>
373
377
 
374
378
  <div className="min-h-0 flex-1 overflow-y-auto">
@@ -550,9 +554,7 @@ export function AssignRoleDialog({
550
554
  : 'border-border/70 bg-background hover:bg-accent/40'
551
555
  }`}
552
556
  >
553
- <div className="flex size-11 shrink-0 items-center justify-center rounded-full bg-muted text-sm font-semibold text-foreground">
554
- {getInitials(user.displayName)}
555
- </div>
557
+ <Avatar label={user.displayName} size="lg" variant="monochrome" />
556
558
  <div className="min-w-0 flex-1">
557
559
  <div className="flex flex-wrap items-center gap-2">
558
560
  <span className="text-sm font-semibold text-foreground">
@@ -672,32 +674,3 @@ export function AssignRoleDialog({
672
674
  )
673
675
  }
674
676
 
675
- function StepBadge({
676
- step,
677
- currentStep,
678
- label,
679
- }: {
680
- step: StepId
681
- currentStep: StepId
682
- label: string
683
- }) {
684
- const isComplete = currentStep > step
685
- const isCurrent = currentStep === step
686
-
687
- return (
688
- <div className="flex items-center gap-2">
689
- <span
690
- className={`flex size-5 items-center justify-center rounded-full border text-xs font-semibold ${
691
- isComplete || isCurrent
692
- ? 'border-foreground bg-foreground text-background'
693
- : 'border-border bg-background text-muted-foreground'
694
- }`}
695
- >
696
- {isComplete ? <Check className="size-3" /> : step}
697
- </span>
698
- <span className={isCurrent ? 'font-semibold text-foreground' : 'text-muted-foreground'}>
699
- {label}
700
- </span>
701
- </div>
702
- )
703
- }
@@ -1,10 +1,10 @@
1
1
  'use client'
2
2
 
3
3
  import * as React from 'react'
4
+ import { Avatar } from '@open-mercato/ui/primitives/avatar'
4
5
  import { Badge } from '@open-mercato/ui/primitives/badge'
5
6
  import { Settings2, SquarePen, Plus, Trash2, UserRoundPlus, ArrowRight } from 'lucide-react'
6
7
  import { useT } from '@open-mercato/shared/lib/i18n/context'
7
- import { getInitials } from './utils'
8
8
 
9
9
  type ChangelogActionType = 'create' | 'edit' | 'delete' | 'assign' | 'system'
10
10
  type ChangelogSource = 'ui' | 'api' | 'system'
@@ -84,9 +84,11 @@ export function ChangelogEntryRow({ entry }: ChangelogEntryRowProps) {
84
84
  </div>
85
85
 
86
86
  <div className="flex items-start gap-2">
87
- <div className="flex size-7 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold text-muted-foreground">
88
- {entry.actorUserId ? getInitials(actorLabel) : <Settings2 className="size-3.5" />}
89
- </div>
87
+ {entry.actorUserId ? (
88
+ <Avatar label={actorLabel} size="sm" variant="monochrome" />
89
+ ) : (
90
+ <Avatar label="" icon={<Settings2 />} size="sm" variant="monochrome" />
91
+ )}
90
92
  <span className="truncate pt-1 text-sm text-foreground">{actorLabel}</span>
91
93
  </div>
92
94
 
@@ -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'
@@ -103,9 +104,12 @@ export function CompanyDetailHeader({
103
104
  {/* Top row: avatar + company info + account manager + actions */}
104
105
  <div className="flex flex-col gap-4 px-6 pt-6 pb-3 sm:flex-row sm:items-start sm:gap-5">
105
106
  {/* Avatar */}
106
- <div className="flex size-18 shrink-0 items-center justify-center rounded-full bg-muted">
107
- <Building2 className="size-7 text-muted-foreground" />
108
- </div>
107
+ <Avatar
108
+ label=""
109
+ icon={<Building2 />}
110
+ size="xl"
111
+ variant="monochrome"
112
+ />
109
113
 
110
114
  {/* Company info */}
111
115
  <div className="min-w-0 flex-1">
@@ -2,10 +2,11 @@
2
2
 
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, type LinkEntityAdapter, type LinkEntityOption } from '../linking/LinkEntityDialog'
10
11
 
11
12
  type LinkedEntityOption = {
@@ -54,49 +55,6 @@ function applyFilter(items: LinkedEntityOption[], query: string): LinkedEntityOp
54
55
  })
55
56
  }
56
57
 
57
- function Pagination({
58
- page,
59
- totalPages,
60
- onPageChange,
61
- }: {
62
- page: number
63
- totalPages: number
64
- onPageChange: (page: number) => void
65
- }) {
66
- if (totalPages <= 1) return null
67
- return (
68
- <div className="flex items-center justify-between border-t border-border/70 pt-3 text-sm text-muted-foreground">
69
- <span>
70
- Page {page} of {totalPages}
71
- </span>
72
- <div className="flex items-center gap-2">
73
- <Button
74
- type="button"
75
- variant="outline"
76
- size="sm"
77
- className="h-8 rounded-lg px-3 text-xs"
78
- onClick={() => onPageChange(Math.max(1, page - 1))}
79
- disabled={page <= 1}
80
- >
81
- <ArrowLeft className="mr-1.5 size-3.5" />
82
- Previous
83
- </Button>
84
- <Button
85
- type="button"
86
- variant="outline"
87
- size="sm"
88
- className="h-8 rounded-lg px-3 text-xs"
89
- onClick={() => onPageChange(Math.min(totalPages, page + 1))}
90
- disabled={page >= totalPages}
91
- >
92
- Next
93
- <ArrowRight className="ml-1.5 size-3.5" />
94
- </Button>
95
- </div>
96
- </div>
97
- )
98
- }
99
-
100
58
  export function DealLinkedEntitiesTab({
101
59
  entityLabel,
102
60
  entityLabelPlural,
@@ -313,11 +271,15 @@ export function DealLinkedEntitiesTab({
313
271
  </div>
314
272
  </Link>
315
273
  ))}
316
- <Pagination
317
- page={visiblePage}
318
- totalPages={visibleTotalPages}
319
- onPageChange={setPage}
320
- />
274
+ {visibleTotalPages > 1 ? (
275
+ <Pagination
276
+ className="border-t border-border/70 pt-3"
277
+ page={visiblePage}
278
+ pageSize={PAGE_SIZE}
279
+ total={visibleTotalPages * PAGE_SIZE}
280
+ onPageChange={setPage}
281
+ />
282
+ ) : null}
321
283
  </div>
322
284
  ) : remoteLinkedLoading ? (
323
285
  <div className="rounded-lg border border-border bg-muted/20 px-5 py-5 text-sm text-muted-foreground">
@@ -63,7 +63,7 @@ export function DetailTabsLayout<TId extends string = string>({
63
63
  className={cn(
64
64
  'h-auto rounded-none border-b-2 px-0 py-1',
65
65
  activeTab === tab.id
66
- ? 'border-primary text-foreground hover:bg-transparent'
66
+ ? 'border-accent-indigo text-foreground hover:bg-transparent'
67
67
  : 'border-transparent text-muted-foreground hover:text-foreground hover:bg-transparent'
68
68
  )}
69
69
  >
@@ -42,8 +42,11 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
42
42
  import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
43
43
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
44
44
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
45
+ import { Badge } from '@open-mercato/ui/primitives/badge'
45
46
  import { Button } from '@open-mercato/ui/primitives/button'
47
+ import { ColorPicker } from '@open-mercato/ui/primitives/color-picker'
46
48
  import { IconButton } from '@open-mercato/ui/primitives/icon-button'
49
+ import { ScrollArea } from '@open-mercato/ui/primitives/scroll-area'
47
50
  import {
48
51
  Dialog,
49
52
  DialogContent,
@@ -421,23 +424,12 @@ function SortableEntryRow({
421
424
  </div>
422
425
 
423
426
  {/* Color picker */}
424
- <div className="flex w-[80px] shrink-0 items-center gap-1.5 rounded-md border border-input px-2 py-1.5">
425
- <label className="relative size-4 shrink-0 cursor-pointer">
426
- <span
427
- className="block size-full rounded-sm"
428
- style={{ backgroundColor: normalizeColor(entry.color) }}
429
- />
430
- <input
431
- type="color"
432
- value={normalizeColor(entry.color)}
433
- onChange={(e) => onColorChange(normalizeColor(e.target.value))}
434
- className="absolute inset-0 size-full cursor-pointer opacity-0"
435
- />
436
- </label>
437
- <span className="text-xs font-medium text-muted-foreground">
438
- {normalizeColor(entry.color)}
439
- </span>
440
- </div>
427
+ <ColorPicker
428
+ value={normalizeColor(entry.color)}
429
+ onChange={(next) => onColorChange(normalizeColor(next))}
430
+ size="sm"
431
+ className="shrink-0"
432
+ />
441
433
 
442
434
  {/* Delete */}
443
435
  <IconButton
@@ -1065,12 +1057,12 @@ export function ManageTagsDialog({ open, onClose }: ManageTagsDialogProps) {
1065
1057
  ) : null}
1066
1058
 
1067
1059
  {/* Tab bar */}
1068
- <div className="flex shrink-0 items-center gap-2 border-b border-border px-4 py-1.5">
1060
+ <div className="flex shrink-0 items-end gap-2 border-b border-input px-4 pt-1.5">
1069
1061
  <IconButton
1070
1062
  type="button"
1071
1063
  variant="ghost"
1072
1064
  size="sm"
1073
- className="size-8 shrink-0 rounded-full"
1065
+ className="size-8 shrink-0 self-center rounded-full"
1074
1066
  onClick={() => scrollCategoryRail('left')}
1075
1067
  disabled={!canScrollLeft}
1076
1068
  aria-label={t('customers.tags.manage.scrollLeft', 'Scroll categories left')}
@@ -1079,7 +1071,7 @@ export function ManageTagsDialog({ open, onClose }: ManageTagsDialogProps) {
1079
1071
  </IconButton>
1080
1072
  <div
1081
1073
  ref={categoryRailRef}
1082
- className="min-w-0 flex-1 overflow-x-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
1074
+ className="scrollbar-hide min-w-0 flex-1 overflow-x-auto"
1083
1075
  >
1084
1076
  <div className="flex items-end gap-0.5">
1085
1077
  {translatedCategories.map((category) => {
@@ -1096,10 +1088,10 @@ export function ManageTagsDialog({ open, onClose }: ManageTagsDialogProps) {
1096
1088
  setActiveTab(category.kind)
1097
1089
  setSearchValue('')
1098
1090
  }}
1099
- className={`flex h-auto shrink-0 items-center gap-1.5 rounded-none border-b-2 px-2.5 py-2 hover:bg-transparent ${
1091
+ className={`flex h-auto shrink-0 items-center gap-1.5 rounded-none border-b-2 px-2.5 py-2 hover:bg-transparent -mb-px ${
1100
1092
  isActive
1101
- ? '-mb-px border-foreground text-foreground'
1102
- : '-mb-px border-transparent text-muted-foreground'
1093
+ ? 'border-accent-indigo text-foreground'
1094
+ : 'border-transparent text-muted-foreground'
1103
1095
  }`}
1104
1096
  >
1105
1097
  <Icon className="size-3.5" />
@@ -1120,7 +1112,7 @@ export function ManageTagsDialog({ open, onClose }: ManageTagsDialogProps) {
1120
1112
  type="button"
1121
1113
  variant="ghost"
1122
1114
  size="sm"
1123
- className="size-8 shrink-0 rounded-full"
1115
+ className="size-8 shrink-0 self-center rounded-full"
1124
1116
  onClick={() => scrollCategoryRail('right')}
1125
1117
  disabled={!canScrollRight}
1126
1118
  aria-label={t('customers.tags.manage.scrollRight', 'Scroll categories right')}
@@ -1130,7 +1122,8 @@ export function ManageTagsDialog({ open, onClose }: ManageTagsDialogProps) {
1130
1122
  </div>
1131
1123
 
1132
1124
  {/* Content */}
1133
- <div className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-6 py-3.5">
1125
+ <ScrollArea className="min-h-0 flex-1" viewportClassName="px-6 py-3.5">
1126
+ <div className="flex flex-col gap-3">
1134
1127
  {activeMeta ? (
1135
1128
  <>
1136
1129
  {/* Category header + search */}
@@ -1141,18 +1134,15 @@ export function ManageTagsDialog({ open, onClose }: ManageTagsDialogProps) {
1141
1134
  {activeMeta.shortLabel}
1142
1135
  </span>
1143
1136
  {(activeMeta.badges ?? []).map((badge) => (
1144
- <span
1137
+ <Badge
1145
1138
  key={badge}
1146
- className={`rounded-sm px-2 py-0.5 text-overline font-bold ${
1147
- badge === 'required'
1148
- ? 'bg-status-warning-bg text-status-warning-text'
1149
- : 'bg-muted text-muted-foreground'
1150
- }`}
1139
+ variant={badge === 'required' ? 'warning' : 'muted'}
1140
+ size="sm"
1151
1141
  >
1152
1142
  {badge === 'required'
1153
1143
  ? t('customers.tags.manage.badge.required', 'REQUIRED')
1154
1144
  : t('customers.tags.manage.badge.system', 'SYSTEM')}
1155
- </span>
1145
+ </Badge>
1156
1146
  ))}
1157
1147
  </div>
1158
1148
  <div className="flex items-center gap-1.5">
@@ -1178,17 +1168,17 @@ export function ManageTagsDialog({ open, onClose }: ManageTagsDialogProps) {
1178
1168
  <div className="flex items-center gap-3 px-3 py-1.5">
1179
1169
  <div className="w-[18px] shrink-0" />
1180
1170
  <div className="min-w-0 flex-1">
1181
- <span className="text-overline font-bold uppercase text-muted-foreground">
1171
+ <span className="text-overline font-medium uppercase tracking-wider text-muted-foreground">
1182
1172
  {t('customers.tags.manage.columns.label', 'LABEL')}
1183
1173
  </span>
1184
1174
  </div>
1185
1175
  <div className="w-[140px] shrink-0">
1186
- <span className="text-overline font-bold uppercase text-muted-foreground">
1176
+ <span className="text-overline font-medium uppercase tracking-wider text-muted-foreground">
1187
1177
  {t('customers.tags.manage.columns.slug', 'SLUG')}
1188
1178
  </span>
1189
1179
  </div>
1190
1180
  <div className="w-[80px] shrink-0">
1191
- <span className="text-overline font-bold uppercase text-muted-foreground">
1181
+ <span className="text-overline font-medium uppercase tracking-wider text-muted-foreground">
1192
1182
  {t('customers.tags.manage.columns.color', 'COLOR')}
1193
1183
  </span>
1194
1184
  </div>
@@ -1283,7 +1273,8 @@ export function ManageTagsDialog({ open, onClose }: ManageTagsDialogProps) {
1283
1273
  {t('customers.tags.manage.noDictionaries', 'No tag categories found.')}
1284
1274
  </div>
1285
1275
  )}
1286
- </div>
1276
+ </div>
1277
+ </ScrollArea>
1287
1278
 
1288
1279
  {/* Separator */}
1289
1280
  <div className="h-px shrink-0 bg-border" />
@@ -7,10 +7,11 @@ import { cn } from '@open-mercato/shared/lib/utils'
7
7
  import { useT } from '@open-mercato/shared/lib/i18n/context'
8
8
  import { Badge } from '@open-mercato/ui/primitives/badge'
9
9
  import { Button } from '@open-mercato/ui/primitives/button'
10
+ import { Avatar } from '@open-mercato/ui/primitives/avatar'
10
11
  import { IconButton } from '@open-mercato/ui/primitives/icon-button'
11
12
  import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
12
13
  import type { CompanyPersonSummary } from './CompanyPeopleSection'
13
- import { formatDate, formatFallbackLabel, getInitials } from './utils'
14
+ import { formatDate, formatFallbackLabel } from './utils'
14
15
 
15
16
  const sourceColorMap: Record<string, string> = {
16
17
  linkedin: 'border-status-info-icon text-status-info-icon',
@@ -70,9 +71,7 @@ export function PersonCard({ person, isStarred, onToggleStar, onUnlink }: Person
70
71
  <div className="min-w-0 overflow-hidden rounded-lg border bg-card p-4">
71
72
  <div className="flex items-start justify-between gap-3">
72
73
  <div className="flex min-w-0 flex-1 items-start gap-3">
73
- <div className="flex size-11 shrink-0 items-center justify-center rounded-full bg-muted text-sm font-bold text-muted-foreground">
74
- {getInitials(person.displayName)}
75
- </div>
74
+ <Avatar label={person.displayName} size="lg" variant="monochrome" />
76
75
  <div className="min-w-0 flex-1 space-y-1">
77
76
  <div className="flex min-w-0 flex-wrap items-center gap-1.5">
78
77
  <span className="min-w-0 break-words text-sm font-bold leading-5 text-foreground">{person.displayName}</span>
@@ -5,6 +5,7 @@ import Link from 'next/link'
5
5
  import { Phone, Mail, Building2, Trash2, Pencil } from 'lucide-react'
6
6
  import { cn } from '@open-mercato/shared/lib/utils'
7
7
  import { useT } from '@open-mercato/shared/lib/i18n/context'
8
+ import { Avatar } from '@open-mercato/ui/primitives/avatar'
8
9
  import { Button } from '@open-mercato/ui/primitives/button'
9
10
  import { IconButton } from '@open-mercato/ui/primitives/icon-button'
10
11
  import { Badge } from '@open-mercato/ui/primitives/badge'
@@ -18,7 +19,7 @@ import type { TagSummary } from './types'
18
19
  import type { TagsSectionController } from '@open-mercato/ui/backend/detail'
19
20
  import type { PersonOverview } from '../formConfig'
20
21
  import type { CustomerDictionaryMap } from '@open-mercato/core/modules/customers/lib/dictionaries'
21
- import { getInitials, formatFallbackLabel } from './utils'
22
+ import { formatFallbackLabel } from './utils'
22
23
 
23
24
  const HEADER_ICON_BUTTON_CLASS = 'size-8 rounded-md'
24
25
 
@@ -125,9 +126,7 @@ export function PersonDetailHeader({
125
126
  <div className="rounded-lg border bg-card px-6 py-5">
126
127
  <div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:gap-5">
127
128
  {/* Avatar */}
128
- <div className="flex size-18 shrink-0 items-center justify-center rounded-full bg-muted text-xl font-bold text-muted-foreground">
129
- {getInitials(displayName)}
130
- </div>
129
+ <Avatar label={displayName} size="xl" variant="monochrome" />
131
130
 
132
131
  {/* Person info */}
133
132
  <div className="min-w-0 flex-1">
@@ -7,11 +7,11 @@ import { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
7
7
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
8
8
  import { LookupSelect, type LookupSelectItem } from '@open-mercato/ui/backend/inputs/LookupSelect'
9
9
  import { IconButton } from '@open-mercato/ui/primitives/icon-button'
10
+ import { Avatar } from '@open-mercato/ui/primitives/avatar'
10
11
  import { Button } from '@open-mercato/ui/primitives/button'
11
12
  import { Badge } from '@open-mercato/ui/primitives/badge'
12
13
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
13
14
  import { fetchAssignableStaffMembers } from './assignableStaff'
14
- import { getInitials } from './utils'
15
15
 
16
16
  export interface RoleAssignment {
17
17
  id: string
@@ -136,9 +136,8 @@ export function RoleAssignmentRow({
136
136
  [role.userEmail, role.userId, role.userName],
137
137
  )
138
138
 
139
- const initials = React.useMemo(() => {
140
- const name = role.userName ?? role.userEmail ?? ''
141
- return getInitials(name || '?')
139
+ const userLabel = React.useMemo(() => {
140
+ return role.userName ?? role.userEmail ?? '?'
142
141
  }, [role.userEmail, role.userName])
143
142
 
144
143
  const displayName = role.userName ?? role.userEmail ?? role.userId
@@ -165,9 +164,7 @@ export function RoleAssignmentRow({
165
164
  </div>
166
165
 
167
166
  <div className="mt-4 flex min-w-0 flex-col gap-3 sm:flex-row sm:items-start">
168
- <div className="flex size-11 shrink-0 items-center justify-center rounded-full bg-muted text-sm font-bold text-muted-foreground">
169
- {initials}
170
- </div>
167
+ <Avatar label={userLabel} size="lg" variant="monochrome" />
171
168
  <div className="min-w-0 flex-1">
172
169
  <div className="break-all text-sm font-semibold leading-5 text-foreground">{displayName}</div>
173
170
  {role.userEmail ? (
@@ -148,13 +148,6 @@ export function createDictionarySelectLabels(
148
148
  }
149
149
  }
150
150
 
151
- export function getInitials(name: string): string {
152
- const words = name.trim().split(/\s+/)
153
- if (words.length === 0 || !words[0]) return '?'
154
- if (words.length === 1) return words[0].charAt(0).toUpperCase()
155
- return (words[0].charAt(0) + words[words.length - 1].charAt(0)).toUpperCase()
156
- }
157
-
158
151
  export function formatCurrency(amount: number, currency?: string | null): string {
159
152
  try {
160
153
  return new Intl.NumberFormat(undefined, {
@@ -24,6 +24,7 @@ import { useAiDock } from '@open-mercato/ui/ai/AiDock'
24
24
  import { useAiChatSessions } from '@open-mercato/ui/ai/AiChatSessions'
25
25
  import { ChatPaneTabs } from '@open-mercato/ui/ai/ChatPaneTabs'
26
26
  import { Button } from '@open-mercato/ui/primitives/button'
27
+ import { ButtonGroup } from '@open-mercato/ui/primitives/button-group'
27
28
  import { IconButton } from '@open-mercato/ui/primitives/icon-button'
28
29
  import {
29
30
  Dialog,
@@ -525,7 +526,7 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
525
526
 
526
527
  return (
527
528
  <>
528
- <div className="inline-flex items-center">
529
+ <ButtonGroup>
529
530
  <Button
530
531
  type="button"
531
532
  variant="outline"
@@ -533,11 +534,7 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
533
534
  data-ai-customers-inject-trigger=""
534
535
  aria-label={triggerLabel}
535
536
  title={triggerLabel}
536
- className={cn(
537
- 'relative',
538
- 'hover:bg-brand-violet/10',
539
- agents.length > 1 && 'rounded-r-none border-r-0',
540
- )}
537
+ className={cn('relative', 'hover:bg-brand-violet/10')}
541
538
  >
542
539
  <AiIcon className="size-4" />
543
540
  <span>{labelText}</span>
@@ -556,10 +553,8 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
556
553
  <IconButton
557
554
  type="button"
558
555
  variant="outline"
559
- size="lg"
560
556
  aria-label={moreAgentsLabel}
561
557
  title={moreAgentsLabel}
562
- className="rounded-l-none"
563
558
  data-ai-customers-inject-picker=""
564
559
  >
565
560
  <ChevronDown className="size-4" aria-hidden />
@@ -593,7 +588,7 @@ export default function AiAssistantTriggerWidget({ context }: AiAssistantTrigger
593
588
  </PopoverContent>
594
589
  </Popover>
595
590
  ) : null}
596
- </div>
591
+ </ButtonGroup>
597
592
  <Dialog open={open} onOpenChange={setOpen}>
598
593
  <DialogContent
599
594
  className={cn(