@open-mercato/core 0.5.1-develop.2996.ce62fd491c → 0.5.1-develop.3032.01699048cb

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 (69) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/api/companies/[id]/route.js +30 -20
  3. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  4. package/dist/modules/customers/api/companies/route.js +12 -7
  5. package/dist/modules/customers/api/companies/route.js.map +2 -2
  6. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +12 -7
  7. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
  8. package/dist/modules/customers/api/people/route.js +12 -7
  9. package/dist/modules/customers/api/people/route.js.map +2 -2
  10. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +21 -0
  11. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  12. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +27 -30
  13. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  14. package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js +56 -0
  15. package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js.map +7 -0
  16. package/dist/modules/customers/components/detail/ActivitiesCard.js +175 -0
  17. package/dist/modules/customers/components/detail/ActivitiesCard.js.map +7 -0
  18. package/dist/modules/customers/components/detail/ActivitiesDayStrip.js +324 -0
  19. package/dist/modules/customers/components/detail/ActivitiesDayStrip.js.map +7 -0
  20. package/dist/modules/customers/components/detail/ActivitiesSection.js +62 -13
  21. package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
  22. package/dist/modules/customers/components/detail/ActivityLogTab.js +14 -23
  23. package/dist/modules/customers/components/detail/ActivityLogTab.js.map +2 -2
  24. package/dist/modules/customers/components/detail/ActivityTimeline.js +13 -13
  25. package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
  26. package/dist/modules/customers/components/detail/ActivityTimelineFilters.js +35 -22
  27. package/dist/modules/customers/components/detail/ActivityTimelineFilters.js.map +2 -2
  28. package/dist/modules/customers/components/detail/AiActionChips.js +15 -22
  29. package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
  30. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +196 -28
  31. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  32. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +2 -2
  33. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
  34. package/dist/modules/customers/components/detail/schedule/FooterFields.js +14 -2
  35. package/dist/modules/customers/components/detail/schedule/FooterFields.js.map +2 -2
  36. package/dist/modules/customers/components/detail/schedule/LinkedEntitiesField.js +9 -2
  37. package/dist/modules/customers/components/detail/schedule/LinkedEntitiesField.js.map +2 -2
  38. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +9 -2
  39. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  40. package/dist/modules/customers/components/detail/schedule/fieldConfig.js +25 -4
  41. package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
  42. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +20 -3
  43. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  44. package/package.json +3 -3
  45. package/src/modules/customers/api/companies/[id]/route.ts +30 -20
  46. package/src/modules/customers/api/companies/route.ts +12 -7
  47. package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +12 -7
  48. package/src/modules/customers/api/people/route.ts +12 -7
  49. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +22 -0
  50. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +28 -21
  51. package/src/modules/customers/components/detail/ActivitiesAddNewMenu.tsx +67 -0
  52. package/src/modules/customers/components/detail/ActivitiesCard.tsx +231 -0
  53. package/src/modules/customers/components/detail/ActivitiesDayStrip.tsx +390 -0
  54. package/src/modules/customers/components/detail/ActivitiesSection.tsx +91 -40
  55. package/src/modules/customers/components/detail/ActivityLogTab.tsx +25 -23
  56. package/src/modules/customers/components/detail/ActivityTimeline.tsx +15 -19
  57. package/src/modules/customers/components/detail/ActivityTimelineFilters.tsx +36 -29
  58. package/src/modules/customers/components/detail/AiActionChips.tsx +17 -23
  59. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +233 -41
  60. package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +6 -2
  61. package/src/modules/customers/components/detail/schedule/FooterFields.tsx +22 -2
  62. package/src/modules/customers/components/detail/schedule/LinkedEntitiesField.tsx +10 -2
  63. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +10 -2
  64. package/src/modules/customers/components/detail/schedule/fieldConfig.ts +26 -6
  65. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +32 -3
  66. package/src/modules/customers/i18n/de.json +69 -2
  67. package/src/modules/customers/i18n/en.json +69 -2
  68. package/src/modules/customers/i18n/es.json +69 -2
  69. package/src/modules/customers/i18n/pl.json +68 -1
@@ -1,12 +1,13 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { Clock } from 'lucide-react'
4
+ import { Clock, Search } from 'lucide-react'
5
5
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
6
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
7
7
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
8
8
  import type { SectionAction, TabEmptyStateConfig } from '@open-mercato/ui/backend/detail'
9
9
  import { Button } from '@open-mercato/ui/primitives/button'
10
+ import { Kbd } from '@open-mercato/ui/primitives/kbd'
10
11
  import { ActivityTimelineFilters } from './ActivityTimelineFilters'
11
12
  import { ActivityTimeline } from './ActivityTimeline'
12
13
  import type { ActivitySummary, InteractionSummary } from './types'
@@ -109,10 +110,42 @@ export function ActivitiesSection({
109
110
  const [filterTypes, setFilterTypes] = React.useState<string[]>([])
110
111
  const [filterDateFrom, setFilterDateFrom] = React.useState('')
111
112
  const [filterDateTo, setFilterDateTo] = React.useState('')
113
+ const [searchTerm, setSearchTerm] = React.useState('')
112
114
  const [activities, setActivities] = React.useState<InteractionSummary[]>([])
113
115
  const [loading, setLoading] = React.useState(false)
114
116
  const [hasMore, setHasMore] = React.useState(false)
115
117
  const [loadedPages, setLoadedPages] = React.useState(1)
118
+ const searchInputRef = React.useRef<HTMLInputElement>(null)
119
+
120
+ React.useEffect(() => {
121
+ if (!entityId) return
122
+ function handleShortcut(event: KeyboardEvent) {
123
+ if ((event.metaKey || event.ctrlKey) && event.key === '1') {
124
+ event.preventDefault()
125
+ searchInputRef.current?.focus()
126
+ }
127
+ }
128
+ window.addEventListener('keydown', handleShortcut)
129
+ return () => window.removeEventListener('keydown', handleShortcut)
130
+ }, [entityId])
131
+
132
+ const visibleActivities = React.useMemo(() => {
133
+ const term = searchTerm.trim().toLowerCase()
134
+ if (!term) return activities
135
+ return activities.filter((activity) => {
136
+ const haystack = [
137
+ activity.title,
138
+ activity.body,
139
+ activity.authorName,
140
+ activity.dealTitle,
141
+ activity.interactionType,
142
+ ]
143
+ .filter((value): value is string => typeof value === 'string' && value.length > 0)
144
+ .join(' ')
145
+ .toLowerCase()
146
+ return haystack.includes(term)
147
+ })
148
+ }, [activities, searchTerm])
116
149
 
117
150
  React.useEffect(() => {
118
151
  onActionChange?.(null)
@@ -269,55 +302,73 @@ export function ActivitiesSection({
269
302
  return () => controller.abort()
270
303
  }, [activities])
271
304
 
305
+ const totalCount = activities.length
306
+ const visibleCount = visibleActivities.length
307
+
272
308
  return (
273
- <div className="rounded-2xl border border-border/70 bg-card p-5">
274
- <div className="mb-4 flex flex-wrap items-center justify-between gap-3">
275
- <div className="flex items-center gap-2">
276
- <Clock className="size-4 text-muted-foreground" />
277
- <h3 className="text-base font-semibold text-foreground">
278
- {entityName
279
- ? t('customers.timeline.history.title', 'Interaction history with {{name}}', { name: entityName })
280
- : t('customers.timeline.history.titleGeneric', 'Interaction history')}
281
- </h3>
282
- </div>
309
+ <div className="flex flex-col gap-3.5 rounded-[10px] border border-border bg-card pt-4 pb-[18px] px-[18px]">
310
+ <div className="flex items-center gap-2">
311
+ <Clock className="size-[15px] text-muted-foreground" />
312
+ <h3 className="text-[13px] font-semibold text-foreground">
313
+ {entityName
314
+ ? t('customers.timeline.history.title', 'Interaction history with {{name}}', { name: entityName })
315
+ : t('customers.timeline.history.titleGeneric', 'Interaction history')}
316
+ </h3>
283
317
  </div>
284
318
 
285
- <div className="mb-4">
286
- <ActivityTimelineFilters
287
- entityId={entityId}
288
- activeTypes={filterTypes}
289
- dateFrom={filterDateFrom}
290
- dateTo={filterDateTo}
291
- onTypesChange={setFilterTypes}
292
- onDateFromChange={setFilterDateFrom}
293
- onDateToChange={setFilterDateTo}
294
- onReset={() => {
295
- setFilterTypes([])
296
- setFilterDateFrom('')
297
- setFilterDateTo('')
298
- }}
319
+ <label className="relative flex items-center">
320
+ <Search className="pointer-events-none absolute left-2.5 size-5 text-muted-foreground" aria-hidden />
321
+ <input
322
+ ref={searchInputRef}
323
+ type="search"
324
+ value={searchTerm}
325
+ onChange={(event) => setSearchTerm(event.target.value)}
326
+ placeholder={t('customers.timeline.history.searchPlaceholder', 'Search...')}
327
+ aria-label={t('customers.timeline.history.searchAriaLabel', 'Search interaction history')}
328
+ className="h-9 w-full rounded-[10px] border border-border bg-card pl-9 pr-14 text-sm text-foreground shadow-xs placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden"
299
329
  />
300
- </div>
330
+ <Kbd className="pointer-events-none absolute right-2 hidden text-[11px] uppercase tracking-[0.48px] sm:inline-flex">
331
+ ⌘1
332
+ </Kbd>
333
+ </label>
334
+
335
+ <ActivityTimelineFilters
336
+ entityId={entityId}
337
+ activeTypes={filterTypes}
338
+ dateFrom={filterDateFrom}
339
+ dateTo={filterDateTo}
340
+ onTypesChange={setFilterTypes}
341
+ onDateFromChange={setFilterDateFrom}
342
+ onDateToChange={setFilterDateTo}
343
+ onReset={() => {
344
+ setFilterTypes([])
345
+ setFilterDateFrom('')
346
+ setFilterDateTo('')
347
+ }}
348
+ />
301
349
 
302
- {loading && activities.length === 0 ? (
350
+ {loading && totalCount === 0 ? (
303
351
  <div className="rounded-lg border border-dashed border-border/70 px-4 py-8 text-sm text-muted-foreground">
304
352
  {t('customers.people.detail.activities.loading', 'Loading activities…')}
305
353
  </div>
306
354
  ) : (
307
355
  <>
308
- <ActivityTimeline activities={activities} onEdit={onEditActivity} />
309
- {activities.length > 0 ? (
310
- <div className="border-t px-5 py-3">
311
- <div className="flex items-center justify-between gap-3">
312
- <span className="text-xs text-muted-foreground">
313
- {t('customers.activities.seeAll', 'See all {count} activities', { count: activities.length })}
314
- </span>
315
- {hasMore ? (
316
- <Button type="button" variant="link" size="sm" onClick={() => setLoadedPages((value) => value + 1)}>
317
- {t('customers.activities.loadMore', 'Load more')}
318
- </Button>
319
- ) : null}
320
- </div>
356
+ <ActivityTimeline activities={visibleActivities} onEdit={onEditActivity} />
357
+ {totalCount > 0 ? (
358
+ <div className="flex items-center justify-between gap-3 border-t border-border/60 pt-3">
359
+ <span className="text-xs text-muted-foreground">
360
+ {searchTerm.trim()
361
+ ? t('customers.activities.seeMatching', 'Showing {visible} of {total} activities', {
362
+ visible: visibleCount,
363
+ total: totalCount,
364
+ })
365
+ : t('customers.activities.seeAll', 'See all {count} activities', { count: totalCount })}
366
+ </span>
367
+ {hasMore ? (
368
+ <Button type="button" variant="link" size="sm" onClick={() => setLoadedPages((value) => value + 1)}>
369
+ {t('customers.activities.loadMore', 'Load more')}
370
+ </Button>
371
+ ) : null}
321
372
  </div>
322
373
  ) : null}
323
374
  </>
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
- import { InlineActivityComposer } from './InlineActivityComposer'
4
- import { PlannedActivitiesSection } from './PlannedActivitiesSection'
3
+ import { ActivitiesCard } from './ActivitiesCard'
4
+ import type { ActivityKind } from './ActivitiesAddNewMenu'
5
5
  import { ActivityHistorySection } from './ActivityHistorySection'
6
6
  import type { InteractionSummary } from './types'
7
7
 
@@ -13,45 +13,47 @@ type GuardedMutationRunner = <T,>(
13
13
  type ActivityLogTabProps = {
14
14
  entityId: string
15
15
  plannedActivities: InteractionSummary[]
16
- onActivityCreated: () => void
16
+ /** @deprecated No longer used after the ActivitiesCard refactor. Kept optional for callers; remove after one minor cycle. */
17
+ onActivityCreated?: () => void
17
18
  onScheduleRequested: () => void
18
- onMarkDone: (id: string) => void
19
+ onAddActivity?: (kind: ActivityKind) => void
20
+ /** @deprecated No longer used after the ActivitiesCard refactor. Kept optional for callers; remove after one minor cycle. */
21
+ onMarkDone?: (id: string) => void
19
22
  onEditActivity: (activity: InteractionSummary) => void
20
- onCancelActivity: (id: string) => void
23
+ /** @deprecated No longer used after the ActivitiesCard refactor. Kept optional for callers; remove after one minor cycle. */
24
+ onCancelActivity?: (id: string) => void
25
+ /** @deprecated No longer used after the ActivitiesCard refactor. Kept optional for callers; remove after one minor cycle. */
21
26
  runGuardedMutation?: GuardedMutationRunner
22
27
  refreshKey?: number
23
28
  useCanonicalInteractions?: boolean
29
+ /** Optional parent-entity company name; surfaces in planned event subtitles when no deal is set. */
30
+ entityCompanyName?: string | null
24
31
  }
25
32
 
26
33
  export function ActivityLogTab({
27
34
  entityId,
28
35
  plannedActivities,
29
- onActivityCreated,
30
36
  onScheduleRequested,
31
- onMarkDone,
37
+ onAddActivity,
32
38
  onEditActivity,
33
- onCancelActivity,
34
- runGuardedMutation,
35
39
  refreshKey = 0,
36
40
  useCanonicalInteractions = false,
41
+ entityCompanyName,
37
42
  }: ActivityLogTabProps) {
43
+ const handleAddNew = (kind: ActivityKind) => {
44
+ if (onAddActivity) onAddActivity(kind)
45
+ else onScheduleRequested()
46
+ }
47
+
38
48
  return (
39
49
  <div className="space-y-4">
40
- <InlineActivityComposer
41
- entityType="company"
50
+ <ActivitiesCard
42
51
  entityId={entityId}
43
- onActivityCreated={onActivityCreated}
44
- runGuardedMutation={runGuardedMutation}
45
- onScheduleRequested={onScheduleRequested}
46
- useCanonicalInteractions={useCanonicalInteractions}
47
- />
48
-
49
- <PlannedActivitiesSection
50
- activities={plannedActivities}
51
- onComplete={onMarkDone}
52
- onSchedule={onScheduleRequested}
53
- onEdit={onEditActivity}
54
- onCancel={onCancelActivity}
52
+ plannedActivities={plannedActivities}
53
+ refreshKey={refreshKey}
54
+ onAddNew={handleAddNew}
55
+ onEditActivity={onEditActivity}
56
+ entityCompanyName={entityCompanyName}
55
57
  />
56
58
 
57
59
  <ActivityHistorySection
@@ -80,52 +80,48 @@ function TimelineEntry({
80
80
 
81
81
  return (
82
82
  <div
83
- className={`px-5 py-4 ${withBorder ? 'border-b border-border/60' : ''} ${onEdit ? 'cursor-pointer hover:bg-accent/50 transition-colors' : ''}`}
83
+ className={`py-2.5 ${withBorder ? 'border-b border-border/60' : ''} ${onEdit ? 'cursor-pointer hover:bg-accent/40 transition-colors' : ''}`}
84
84
  onClick={() => onEdit?.(activity)}
85
85
  role={onEdit ? 'button' : undefined}
86
86
  tabIndex={onEdit ? 0 : undefined}
87
87
  onKeyDown={onEdit ? (e) => { if (e.key === 'Enter') onEdit(activity) } : undefined}
88
88
  >
89
- <div className="grid items-start gap-3" style={{ gridTemplateColumns: '72px 40px 1fr' }}>
89
+ <div className="grid items-start gap-3" style={{ gridTemplateColumns: '75px 32px 1fr' }}>
90
90
  {/* Column 1: Date */}
91
- <div className="shrink-0 pt-0.5">
92
- <span className="block text-xs font-bold leading-tight">
91
+ <div className="shrink-0">
92
+ <span className="block text-[11px] font-semibold leading-tight text-foreground">
93
93
  {formatRelativeDate(dateStr, t)}
94
94
  </span>
95
- <span className="block text-xs leading-tight text-muted-foreground">
95
+ <span className="block text-[10px] leading-tight text-muted-foreground">
96
96
  {formatTime(dateStr)}
97
97
  </span>
98
98
  </div>
99
99
 
100
100
  {/* Column 2: Type icon */}
101
- <div className="flex size-10 items-center justify-center rounded-lg bg-muted shrink-0">
102
- {TypeIcon ? <TypeIcon className="size-4 text-muted-foreground" /> : null}
101
+ <div className="flex size-8 items-center justify-center rounded-md bg-muted shrink-0">
102
+ {TypeIcon ? <TypeIcon className="size-3.5 text-muted-foreground" /> : null}
103
103
  </div>
104
104
 
105
105
  {/* Column 3: Content */}
106
- <div className="min-w-0">
107
- <div className="flex items-center gap-1.5">
108
- <span className="text-sm font-semibold leading-5">
109
- {title}{duration}
110
- </span>
111
- </div>
106
+ <div className="min-w-0 space-y-1.5">
107
+ <span className="block text-[12px] font-semibold leading-tight text-foreground">
108
+ {title}{duration}
109
+ </span>
112
110
 
113
111
  {activity.body && activity.title && (
114
- <p className="mt-1 text-sm text-muted-foreground">
112
+ <p className="text-[11px] leading-snug text-muted-foreground">
115
113
  {activity.body}
116
114
  </p>
117
115
  )}
118
116
 
119
117
  {activity.authorName && (
120
- <div className="mt-1.5 flex items-center gap-1 text-xs text-muted-foreground">
121
- <User className="size-3 shrink-0" />
118
+ <div className="flex items-center gap-1 text-[10px] text-muted-foreground">
119
+ <User className="size-2.5 shrink-0" />
122
120
  <span>{t('customers.timeline.author', 'by {{name}}', { name: activity.authorName })}</span>
123
121
  </div>
124
122
  )}
125
123
 
126
- <div className="mt-2">
127
- <AiActionChips activityType={activity.interactionType} />
128
- </div>
124
+ <AiActionChips activityType={activity.interactionType} />
129
125
  </div>
130
126
  </div>
131
127
  </div>
@@ -4,14 +4,15 @@ import { Phone, Mail, Users, StickyNote, SlidersHorizontal } from 'lucide-react'
4
4
  import { cn } from '@open-mercato/shared/lib/utils'
5
5
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
6
  import { Button } from '@open-mercato/ui/primitives/button'
7
+ import { IconButton } from '@open-mercato/ui/primitives/icon-button'
7
8
  import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
8
9
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
9
10
 
10
11
  const FILTER_TYPES = [
12
+ { type: 'note', icon: StickyNote },
11
13
  { type: 'call', icon: Phone },
12
- { type: 'email', icon: Mail },
13
14
  { type: 'meeting', icon: Users },
14
- { type: 'note', icon: StickyNote },
15
+ { type: 'email', icon: Mail },
15
16
  ] as const
16
17
 
17
18
  type InteractionCounts = {
@@ -33,6 +34,10 @@ interface ActivityTimelineFiltersProps {
33
34
  onReset: () => void
34
35
  }
35
36
 
37
+ const CHIP_BASE = 'inline-flex h-7 items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium transition-colors'
38
+ const CHIP_INACTIVE = 'border border-border bg-card text-muted-foreground hover:bg-accent/40'
39
+ const CHIP_ACTIVE = 'border border-status-info-border bg-status-info-bg text-status-info-text'
40
+
36
41
  export function ActivityTimelineFilters({
37
42
  entityId,
38
43
  activeTypes,
@@ -45,6 +50,7 @@ export function ActivityTimelineFilters({
45
50
  }: ActivityTimelineFiltersProps) {
46
51
  const t = useT()
47
52
  const hasActiveFilters = activeTypes.length > 0 || dateFrom || dateTo
53
+ const allActive = activeTypes.length === 0
48
54
  const [counts, setCounts] = React.useState<InteractionCounts | null>(null)
49
55
 
50
56
  React.useEffect(() => {
@@ -72,54 +78,55 @@ export function ActivityTimelineFilters({
72
78
  }
73
79
  }, [activeTypes, onTypesChange])
74
80
 
81
+ const handleSelectAll = React.useCallback(() => {
82
+ onTypesChange([])
83
+ }, [onTypesChange])
84
+
75
85
  return (
76
- <div className="flex flex-col gap-3 border-b border-border/60 pb-4 md:flex-row md:items-center md:justify-between">
77
- <div className="flex flex-wrap items-center gap-2">
78
- <span className="text-overline font-bold uppercase tracking-wider text-muted-foreground">
79
- {t('customers.people.detail.activities.filterLabel', 'FILTER:')}
80
- </span>
86
+ <div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
87
+ <div className="flex flex-wrap items-center gap-2.5">
88
+ <button
89
+ type="button"
90
+ onClick={handleSelectAll}
91
+ aria-pressed={allActive}
92
+ className={cn(CHIP_BASE, allActive ? CHIP_ACTIVE : CHIP_INACTIVE)}
93
+ >
94
+ <span>{t('customers.timeline.filter.all', 'All Activities')}</span>
95
+ </button>
81
96
 
82
97
  {FILTER_TYPES.map(({ type, icon: Icon }) => {
83
98
  const isActive = activeTypes.includes(type)
84
99
  const count = counts?.[type as keyof InteractionCounts]
100
+ const hasCount = typeof count === 'number' && count > 0
85
101
  return (
86
- <Button
102
+ <button
87
103
  key={type}
88
104
  type="button"
89
- variant="outline"
90
- size="sm"
91
105
  onClick={() => handleTypeToggle(type)}
92
- className={cn(
93
- 'h-7 rounded-full px-2.5 text-xs gap-1.5',
94
- isActive
95
- ? 'border-foreground bg-background text-foreground'
96
- : 'border-border bg-background text-muted-foreground',
97
- )}
98
106
  aria-pressed={isActive}
107
+ className={cn(CHIP_BASE, isActive ? CHIP_ACTIVE : CHIP_INACTIVE)}
99
108
  >
100
- <Icon className="size-2.5" />
101
- <span className="font-semibold">{t(`customers.timeline.filter.${type}`, type)}</span>
102
- {typeof count === 'number' && count > 0 ? (
103
- <span className="rounded-full bg-muted px-1 text-overline leading-4 text-muted-foreground">
104
- {count}
105
- </span>
106
- ) : null}
107
- </Button>
109
+ <Icon className="size-[18px] shrink-0" />
110
+ <span>
111
+ {t(`customers.timeline.filter.${type}`, type)}
112
+ {hasCount ? ` ${count}` : ''}
113
+ </span>
114
+ </button>
108
115
  )
109
116
  })}
110
117
  </div>
111
118
 
112
119
  <Popover>
113
120
  <PopoverTrigger asChild>
114
- <Button
121
+ <IconButton
115
122
  type="button"
116
123
  variant="outline"
117
124
  size="sm"
118
- className="h-7 rounded-md px-2.5 text-xs text-muted-foreground"
125
+ className="size-7 rounded-md text-muted-foreground"
126
+ aria-label={t('customers.people.detail.activities.moreFilters', 'More filters')}
119
127
  >
120
- <SlidersHorizontal className="size-2.5" />
121
- {t('customers.people.detail.activities.moreFilters', 'More')}
122
- </Button>
128
+ <SlidersHorizontal className="size-3.5" />
129
+ </IconButton>
123
130
  </PopoverTrigger>
124
131
  <PopoverContent align="end" className="w-72 space-y-3">
125
132
  <div className="space-y-1.5">
@@ -3,7 +3,6 @@
3
3
  import * as React from 'react'
4
4
  import { Sparkles } from 'lucide-react'
5
5
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
- import { Button } from '@open-mercato/ui/primitives/button'
7
6
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@open-mercato/ui/primitives/tooltip'
8
7
  import { AI_TIMELINE_ACTIONS_BY_TYPE, resolveAiActions } from './aiActionCatalog'
9
8
 
@@ -17,30 +16,25 @@ export function AiActionChips({ activityType }: AiActionChipsProps) {
17
16
 
18
17
  return (
19
18
  <TooltipProvider delayDuration={300}>
20
- <div className="flex items-center gap-0.5">
21
- <span className="mr-1 text-xs text-muted-foreground">
19
+ <div className="flex items-center gap-1.5">
20
+ <span className="text-[9px] font-bold text-muted-foreground/70">
22
21
  {t('customers.ai.prefix', 'AI:')}
23
22
  </span>
24
- {actions.map((action, index) => (
25
- <React.Fragment key={action.key}>
26
- {index > 0 && <span className="text-xs text-muted-foreground/40">|</span>}
27
- <Tooltip>
28
- <TooltipTrigger asChild>
29
- <Button
30
- type="button"
31
- variant="ghost"
32
- size="sm"
33
- className="h-auto inline-flex items-center gap-0.5 px-1 py-0.5 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
34
- >
35
- <Sparkles className="size-2.5" />
36
- {t(action.i18nKey, action.fallback)}
37
- </Button>
38
- </TooltipTrigger>
39
- <TooltipContent side="bottom" className="text-xs">
40
- {t('customers.ai.comingSoon', 'Coming soon')}
41
- </TooltipContent>
42
- </Tooltip>
43
- </React.Fragment>
23
+ {actions.map((action) => (
24
+ <Tooltip key={action.key}>
25
+ <TooltipTrigger asChild>
26
+ <button
27
+ type="button"
28
+ className="inline-flex items-center gap-1 rounded-[4px] border border-dashed border-border bg-card pl-1.5 pr-[7px] py-[3px] text-[9px] font-medium text-muted-foreground/70 transition-colors hover:border-muted-foreground hover:text-foreground"
29
+ >
30
+ <Sparkles className="size-2.5 shrink-0" />
31
+ {t(action.i18nKey, action.fallback)}
32
+ </button>
33
+ </TooltipTrigger>
34
+ <TooltipContent side="bottom" className="text-xs">
35
+ {t('customers.ai.comingSoon', 'Coming soon')}
36
+ </TooltipContent>
37
+ </Tooltip>
44
38
  ))}
45
39
  </div>
46
40
  </TooltipProvider>