@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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/customers/api/companies/[id]/route.js +30 -20
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/companies/route.js +12 -7
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +12 -7
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +12 -7
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +21 -0
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +27 -30
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js +56 -0
- package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js.map +7 -0
- package/dist/modules/customers/components/detail/ActivitiesCard.js +175 -0
- package/dist/modules/customers/components/detail/ActivitiesCard.js.map +7 -0
- package/dist/modules/customers/components/detail/ActivitiesDayStrip.js +324 -0
- package/dist/modules/customers/components/detail/ActivitiesDayStrip.js.map +7 -0
- package/dist/modules/customers/components/detail/ActivitiesSection.js +62 -13
- package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityLogTab.js +14 -23
- package/dist/modules/customers/components/detail/ActivityLogTab.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityTimeline.js +13 -13
- package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityTimelineFilters.js +35 -22
- package/dist/modules/customers/components/detail/ActivityTimelineFilters.js.map +2 -2
- package/dist/modules/customers/components/detail/AiActionChips.js +15 -22
- package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +196 -28
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/FooterFields.js +14 -2
- package/dist/modules/customers/components/detail/schedule/FooterFields.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/LinkedEntitiesField.js +9 -2
- package/dist/modules/customers/components/detail/schedule/LinkedEntitiesField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +9 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js +25 -4
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +20 -3
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
- package/package.json +3 -3
- package/src/modules/customers/api/companies/[id]/route.ts +30 -20
- package/src/modules/customers/api/companies/route.ts +12 -7
- package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +12 -7
- package/src/modules/customers/api/people/route.ts +12 -7
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +22 -0
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +28 -21
- package/src/modules/customers/components/detail/ActivitiesAddNewMenu.tsx +67 -0
- package/src/modules/customers/components/detail/ActivitiesCard.tsx +231 -0
- package/src/modules/customers/components/detail/ActivitiesDayStrip.tsx +390 -0
- package/src/modules/customers/components/detail/ActivitiesSection.tsx +91 -40
- package/src/modules/customers/components/detail/ActivityLogTab.tsx +25 -23
- package/src/modules/customers/components/detail/ActivityTimeline.tsx +15 -19
- package/src/modules/customers/components/detail/ActivityTimelineFilters.tsx +36 -29
- package/src/modules/customers/components/detail/AiActionChips.tsx +17 -23
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +233 -41
- package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +6 -2
- package/src/modules/customers/components/detail/schedule/FooterFields.tsx +22 -2
- package/src/modules/customers/components/detail/schedule/LinkedEntitiesField.tsx +10 -2
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +10 -2
- package/src/modules/customers/components/detail/schedule/fieldConfig.ts +26 -6
- package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +32 -3
- package/src/modules/customers/i18n/de.json +69 -2
- package/src/modules/customers/i18n/en.json +69 -2
- package/src/modules/customers/i18n/es.json +69 -2
- 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-
|
|
274
|
-
<div className="
|
|
275
|
-
<
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
{entityName
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
<
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
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 &&
|
|
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={
|
|
309
|
-
{
|
|
310
|
-
<div className="border-t
|
|
311
|
-
<
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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 {
|
|
4
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
41
|
-
entityType="company"
|
|
50
|
+
<ActivitiesCard
|
|
42
51
|
entityId={entityId}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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={`
|
|
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: '
|
|
89
|
+
<div className="grid items-start gap-3" style={{ gridTemplateColumns: '75px 32px 1fr' }}>
|
|
90
90
|
{/* Column 1: Date */}
|
|
91
|
-
<div className="shrink-0
|
|
92
|
-
<span className="block text-
|
|
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-
|
|
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-
|
|
102
|
-
{TypeIcon ? <TypeIcon className="size-
|
|
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
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
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="
|
|
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="
|
|
121
|
-
<User className="size-
|
|
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
|
-
<
|
|
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: '
|
|
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
|
|
77
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
<
|
|
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-
|
|
101
|
-
<span
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
<
|
|
121
|
+
<IconButton
|
|
115
122
|
type="button"
|
|
116
123
|
variant="outline"
|
|
117
124
|
size="sm"
|
|
118
|
-
className="
|
|
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-
|
|
121
|
-
|
|
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-
|
|
21
|
-
<span className="
|
|
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
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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>
|