@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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/detail/schedule/useScheduleFormState.ts"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react'\nimport type { ActivityType } from './fieldConfig'\n\nexport type RsvpStatus = 'pending' | 'accepted' | 'declined' | 'tentative'\n\nexport type Participant = {\n userId: string\n name: string\n email?: string\n color?: string\n status?: RsvpStatus\n}\n\nexport type LinkedEntity = {\n id: string\n type: 'company' | 'deal' | 'offer'\n label: string\n}\n\nexport type ScheduleActivityEditData = {\n id: string\n interactionType?: string\n title?: string | null\n body?: string | null\n scheduledAt?: string | null\n durationMinutes?: number | null\n location?: string | null\n allDay?: boolean | null\n recurrenceRule?: string | null\n recurrenceEnd?: string | null\n participants?: Array<{ userId: string; name?: string; email?: string; status?: string }> | null\n reminderMinutes?: number | null\n visibility?: string | null\n linkedEntities?: Array<{ id: string; type: string; label: string }> | null\n guestPermissions?: { canInviteOthers?: boolean; canModify?: boolean; canSeeList?: boolean } | null\n}\n\nexport const PARTICIPANT_COLORS = [\n 'bg-chart-emerald',\n 'bg-chart-blue',\n 'bg-chart-orange',\n 'bg-chart-violet',\n 'bg-chart-pink',\n 'bg-chart-teal',\n]\n\ninterface UseScheduleFormStateParams {\n open: boolean\n editData: ScheduleActivityEditData | null | undefined\n}\n\nexport function useScheduleFormState({ open, editData }: UseScheduleFormStateParams) {\n const [activityType, setActivityType] = React.useState<ActivityType>('meeting')\n const [title, setTitle] = React.useState('')\n const [date, setDate] = React.useState(() => new Date().toISOString().slice(0, 10))\n const [startTime, setStartTime] = React.useState('10:00')\n const [duration, setDuration] = React.useState(30)\n const [allDay, setAllDay] = React.useState(false)\n const [description, setDescription] = React.useState('')\n const [markdownEnabled, setMarkdownEnabled] = React.useState(true)\n const [location, setLocation] = React.useState('')\n const [reminderMinutes, setReminderMinutes] = React.useState(15)\n const [visibility, setVisibility] = React.useState('team')\n const [participants, setParticipants] = React.useState<Participant[]>([])\n const [linkedEntities, setLinkedEntities] = React.useState<LinkedEntity[]>([])\n const [recurrenceEnabled, setRecurrenceEnabled] = React.useState(false)\n const [recurrenceDays, setRecurrenceDays] = React.useState<boolean[]>([true, false, true, false, false, false, false])\n const [recurrenceEndType, setRecurrenceEndType] = React.useState<'never' | 'count' | 'date'>('never')\n const [recurrenceCount, setRecurrenceCount] = React.useState(8)\n const [recurrenceEndDate, setRecurrenceEndDate] = React.useState('')\n const [conflict, setConflict] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [guestPermissions, setGuestPermissions] = React.useState({ canInviteOthers: true, canModify: false, canSeeList: true })\n\n React.useEffect(() => {\n if (open) {\n if (editData) {\n // Edit mode: populate from existing interaction\n
|
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;AAqChB,MAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,SAAS,qBAAqB,EAAE,MAAM,SAAS,GAA+B;AACnF,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAuB,SAAS;AAC9E,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,EAAE;AAC3C,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,OAAM,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAClF,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,OAAO;AACxD,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,EAAE;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,EAAE;AACvD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,IAAI;AACjE,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,EAAE;AACjD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,EAAE;AAC/D,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,MAAM;AACzD,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAwB,CAAC,CAAC;AACxE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAyB,CAAC,CAAC;AAC7E,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,KAAK;AACtE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAoB,CAAC,MAAM,OAAO,MAAM,OAAO,OAAO,OAAO,KAAK,CAAC;AACrH,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAqC,OAAO;AACpG,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,CAAC;AAC9D,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,EAAE;AACnE,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAwB,IAAI;AAClE,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAS,EAAE,iBAAiB,MAAM,WAAW,OAAO,YAAY,KAAK,CAAC;AAE5H,QAAM,UAAU,MAAM;AACpB,QAAI,MAAM;AACR,UAAI,UAAU;AAEZ,
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\nimport type { ActivityType } from './fieldConfig'\n\nexport type RsvpStatus = 'pending' | 'accepted' | 'declined' | 'tentative'\n\nexport type Participant = {\n userId: string\n name: string\n email?: string\n color?: string\n status?: RsvpStatus\n}\n\nexport type LinkedEntity = {\n id: string\n type: 'company' | 'deal' | 'offer'\n label: string\n}\n\nexport type ScheduleActivityEditData = {\n id: string\n interactionType?: string\n title?: string | null\n body?: string | null\n scheduledAt?: string | null\n durationMinutes?: number | null\n location?: string | null\n allDay?: boolean | null\n recurrenceRule?: string | null\n recurrenceEnd?: string | null\n participants?: Array<{ userId: string; name?: string; email?: string; status?: string }> | null\n reminderMinutes?: number | null\n visibility?: string | null\n linkedEntities?: Array<{ id: string; type: string; label: string }> | null\n guestPermissions?: { canInviteOthers?: boolean; canModify?: boolean; canSeeList?: boolean } | null\n}\n\nexport const PARTICIPANT_COLORS = [\n 'bg-chart-emerald',\n 'bg-chart-blue',\n 'bg-chart-orange',\n 'bg-chart-violet',\n 'bg-chart-pink',\n 'bg-chart-teal',\n]\n\n// Per-Figma defaults for the Reminder dropdown when the user picks an activity\n// type. Meeting/email keep the standard 15 min; tasks default to 1 day (1440 min)\n// because they're plan-ahead artefacts; calls default to 5 min as a stand-in for\n// the Figma \"After call ends\" treatment (which would need a non-numeric sentinel\n// in the API contract \u2014 tracked as a follow-up).\nconst DEFAULT_REMINDER_MINUTES: Record<ActivityType, number> = {\n meeting: 15,\n call: 5,\n task: 1440,\n email: 15,\n}\n\ninterface UseScheduleFormStateParams {\n open: boolean\n editData: ScheduleActivityEditData | null | undefined\n}\n\nexport function useScheduleFormState({ open, editData }: UseScheduleFormStateParams) {\n const [activityType, setActivityType] = React.useState<ActivityType>('meeting')\n const [title, setTitle] = React.useState('')\n const [date, setDate] = React.useState(() => new Date().toISOString().slice(0, 10))\n const [startTime, setStartTime] = React.useState('10:00')\n const [duration, setDuration] = React.useState(30)\n const [allDay, setAllDay] = React.useState(false)\n const [description, setDescription] = React.useState('')\n const [markdownEnabled, setMarkdownEnabled] = React.useState(true)\n const [location, setLocation] = React.useState('')\n const [reminderMinutes, setReminderMinutes] = React.useState(15)\n const [visibility, setVisibility] = React.useState('team')\n const [participants, setParticipants] = React.useState<Participant[]>([])\n const [linkedEntities, setLinkedEntities] = React.useState<LinkedEntity[]>([])\n const [recurrenceEnabled, setRecurrenceEnabled] = React.useState(false)\n const [recurrenceDays, setRecurrenceDays] = React.useState<boolean[]>([true, false, true, false, false, false, false])\n const [recurrenceEndType, setRecurrenceEndType] = React.useState<'never' | 'count' | 'date'>('never')\n const [recurrenceCount, setRecurrenceCount] = React.useState(8)\n const [recurrenceEndDate, setRecurrenceEndDate] = React.useState('')\n const [conflict, setConflict] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [guestPermissions, setGuestPermissions] = React.useState({ canInviteOthers: true, canModify: false, canSeeList: true })\n\n React.useEffect(() => {\n if (open) {\n if (editData) {\n // Edit mode: populate from existing interaction\n const resolvedType = (editData.interactionType as ActivityType) ?? 'meeting'\n setActivityType(resolvedType)\n setTitle(editData.title ?? '')\n const scheduledDate = editData.scheduledAt ? new Date(editData.scheduledAt) : new Date()\n setDate(scheduledDate.toISOString().slice(0, 10))\n setStartTime(scheduledDate.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }))\n setDuration(editData.durationMinutes ?? 30)\n setAllDay(editData.allDay ?? false)\n setDescription(editData.body ?? '')\n setLocation(editData.location ?? '')\n // Use per-type default when the editData omits an explicit reminder\n // (the menu-driven \"New X\" flow opens the dialog with `reminderMinutes: null`).\n setReminderMinutes(editData.reminderMinutes ?? DEFAULT_REMINDER_MINUTES[resolvedType])\n setVisibility(editData.visibility ?? 'team')\n setParticipants(\n Array.isArray(editData.participants)\n ? editData.participants.map((p, i) => ({\n userId: p.userId,\n name: p.name ?? p.userId,\n email: p.email,\n color: PARTICIPANT_COLORS[i % PARTICIPANT_COLORS.length],\n status: (p.status ?? 'pending') as RsvpStatus,\n }))\n : [],\n )\n setLinkedEntities(\n Array.isArray(editData.linkedEntities)\n ? editData.linkedEntities.map((e) => ({ id: e.id, type: e.type as LinkedEntity['type'], label: e.label }))\n : [],\n )\n if (editData.recurrenceRule) {\n setRecurrenceEnabled(true)\n // Parse RRULE to set days and end type\n const rule = editData.recurrenceRule\n const byDayMatch = rule.match(/BYDAY=([A-Z,]+)/)\n if (byDayMatch) {\n const dayNames = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']\n const activeDays = byDayMatch[1].split(',')\n setRecurrenceDays(dayNames.map((d) => activeDays.includes(d)))\n }\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const untilMatch = rule.match(/UNTIL=(\\d{8})/)\n if (countMatch) {\n setRecurrenceEndType('count')\n setRecurrenceCount(Number(countMatch[1]))\n } else if (untilMatch) {\n setRecurrenceEndType('date')\n const raw = untilMatch[1]\n setRecurrenceEndDate(`${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`)\n } else {\n setRecurrenceEndType('never')\n }\n } else {\n setRecurrenceEnabled(false)\n }\n if (editData.guestPermissions) {\n setGuestPermissions({\n canInviteOthers: editData.guestPermissions.canInviteOthers ?? true,\n canModify: editData.guestPermissions.canModify ?? false,\n canSeeList: editData.guestPermissions.canSeeList ?? true,\n })\n }\n } else {\n // Create mode: reset all fields\n setActivityType('meeting')\n setTitle('')\n setDate(new Date().toISOString().slice(0, 10))\n setStartTime('10:00')\n setDuration(30)\n setAllDay(false)\n setDescription('')\n setLocation('')\n setReminderMinutes(DEFAULT_REMINDER_MINUTES.meeting)\n setVisibility('team')\n setParticipants([])\n setLinkedEntities([])\n setRecurrenceEnabled(false)\n }\n setConflict(null)\n }\n return () => {\n // Safety net: restore body scroll if Radix Dialog fails to clean up\n document.body.style.removeProperty('overflow')\n document.body.style.removeProperty('pointer-events')\n }\n }, [open, editData])\n\n // Update the Reminder default when the activity type changes in create mode.\n // Skipped in edit mode (the persisted value wins), and gated by `open` to\n // avoid flipping the default in a closed-but-mounted dialog.\n const lastReminderTypeRef = React.useRef<ActivityType>('meeting')\n React.useEffect(() => {\n if (!open || editData) {\n lastReminderTypeRef.current = activityType\n return\n }\n if (lastReminderTypeRef.current === activityType) return\n lastReminderTypeRef.current = activityType\n setReminderMinutes(DEFAULT_REMINDER_MINUTES[activityType])\n }, [activityType, editData, open])\n\n const removeParticipant = React.useCallback((userId: string) => {\n setParticipants((prev) => prev.filter((p) => p.userId !== userId))\n }, [])\n\n const toggleRecurrenceDay = React.useCallback((index: number) => {\n setRecurrenceDays((prev) => {\n const next = [...prev]\n next[index] = !next[index]\n return next\n })\n }, [])\n\n return {\n activityType,\n setActivityType,\n title,\n setTitle,\n date,\n setDate,\n startTime,\n setStartTime,\n duration,\n setDuration,\n allDay,\n setAllDay,\n description,\n setDescription,\n markdownEnabled,\n setMarkdownEnabled,\n location,\n setLocation,\n reminderMinutes,\n setReminderMinutes,\n visibility,\n setVisibility,\n participants,\n setParticipants,\n linkedEntities,\n setLinkedEntities,\n recurrenceEnabled,\n setRecurrenceEnabled,\n recurrenceDays,\n setRecurrenceDays,\n recurrenceEndType,\n setRecurrenceEndType,\n recurrenceCount,\n setRecurrenceCount,\n recurrenceEndDate,\n setRecurrenceEndDate,\n conflict,\n setConflict,\n saving,\n setSaving,\n guestPermissions,\n setGuestPermissions,\n removeParticipant,\n toggleRecurrenceDay,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.5.1-develop.
|
|
3
|
+
"version": "0.5.1-develop.3032.01699048cb",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -237,10 +237,10 @@
|
|
|
237
237
|
"ts-pattern": "^5.0.0"
|
|
238
238
|
},
|
|
239
239
|
"peerDependencies": {
|
|
240
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
240
|
+
"@open-mercato/shared": "0.5.1-develop.3032.01699048cb"
|
|
241
241
|
},
|
|
242
242
|
"devDependencies": {
|
|
243
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
243
|
+
"@open-mercato/shared": "0.5.1-develop.3032.01699048cb",
|
|
244
244
|
"@testing-library/dom": "^10.4.1",
|
|
245
245
|
"@testing-library/jest-dom": "^6.9.1",
|
|
246
246
|
"@testing-library/react": "^16.3.1",
|
|
@@ -42,7 +42,10 @@ import type { EntityId } from '@open-mercato/shared/modules/entities'
|
|
|
42
42
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
43
43
|
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
44
44
|
import { parseBooleanFromUnknown } from '@open-mercato/shared/lib/boolean'
|
|
45
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
filterActivePersonCompanyLinks,
|
|
47
|
+
withActiveCustomerPersonCompanyLinkFilter,
|
|
48
|
+
} from '../../../lib/personCompanyLinkTable'
|
|
46
49
|
import { normalizeCustomerDetailCustomFields } from '../../detailCustomFields'
|
|
47
50
|
|
|
48
51
|
export const metadata = {
|
|
@@ -703,15 +706,17 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
703
706
|
},
|
|
704
707
|
'customers.companies.GET',
|
|
705
708
|
)
|
|
706
|
-
const companyLinks =
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
709
|
+
const companyLinks = filterActivePersonCompanyLinks(
|
|
710
|
+
await findWithDecryption(
|
|
711
|
+
em,
|
|
712
|
+
CustomerPersonCompanyLink,
|
|
713
|
+
companyLinkWhere,
|
|
714
|
+
{
|
|
715
|
+
populate: ['person', 'person.personProfile'],
|
|
716
|
+
orderBy: { isPrimary: 'desc', createdAt: 'asc' },
|
|
717
|
+
},
|
|
718
|
+
peopleDecryptionScope,
|
|
719
|
+
),
|
|
715
720
|
)
|
|
716
721
|
companyLinks.forEach((link) => {
|
|
717
722
|
const entity = typeof link.person === 'string' ? null : link.person
|
|
@@ -829,18 +834,23 @@ export async function GET(_req: Request, ctx: { params?: { id?: string } }) {
|
|
|
829
834
|
})
|
|
830
835
|
const peopleCount = includePeople
|
|
831
836
|
? relatedPeople.length
|
|
832
|
-
:
|
|
833
|
-
|
|
834
|
-
await withActiveCustomerPersonCompanyLinkFilter(
|
|
837
|
+
: filterActivePersonCompanyLinks(
|
|
838
|
+
await findWithDecryption(
|
|
835
839
|
em,
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
840
|
+
CustomerPersonCompanyLink,
|
|
841
|
+
await withActiveCustomerPersonCompanyLinkFilter(
|
|
842
|
+
em,
|
|
843
|
+
{
|
|
844
|
+
company: company.id,
|
|
845
|
+
organizationId: company.organizationId,
|
|
846
|
+
tenantId: company.tenantId,
|
|
847
|
+
},
|
|
848
|
+
'customers.companies.GET',
|
|
849
|
+
),
|
|
850
|
+
{},
|
|
851
|
+
{ tenantId: company.tenantId, organizationId: company.organizationId },
|
|
842
852
|
),
|
|
843
|
-
)
|
|
853
|
+
).length
|
|
844
854
|
const kpiInteractionRows = canonicalActiveInteractions.length
|
|
845
855
|
? canonicalActiveInteractions
|
|
846
856
|
: await findWithDecryption(
|
|
@@ -33,7 +33,10 @@ import {
|
|
|
33
33
|
createPagedListResponseSchema,
|
|
34
34
|
defaultOkResponseSchema,
|
|
35
35
|
} from '../openapi'
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
filterActivePersonCompanyLinks,
|
|
38
|
+
withActiveCustomerPersonCompanyLinkFilter,
|
|
39
|
+
} from '../../lib/personCompanyLinkTable'
|
|
37
40
|
import { normalizeCompanyProfilePayload } from './payload'
|
|
38
41
|
|
|
39
42
|
const rawBodySchema = z.object({}).passthrough()
|
|
@@ -217,12 +220,14 @@ const crud = makeCrudRoute({
|
|
|
217
220
|
{ person: query.excludeLinkedPersonId },
|
|
218
221
|
'customers.companies.GET',
|
|
219
222
|
)
|
|
220
|
-
const links =
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
const links = filterActivePersonCompanyLinks(
|
|
224
|
+
await findWithDecryption(
|
|
225
|
+
em,
|
|
226
|
+
CustomerPersonCompanyLink,
|
|
227
|
+
linkWhere,
|
|
228
|
+
{ populate: ['company'] },
|
|
229
|
+
decryptionScope,
|
|
230
|
+
),
|
|
226
231
|
)
|
|
227
232
|
links.forEach((link) => {
|
|
228
233
|
const companyId = link.company?.id
|
|
@@ -20,7 +20,10 @@ import {
|
|
|
20
20
|
CustomerDeal,
|
|
21
21
|
CustomerInteraction,
|
|
22
22
|
} from '../../../../../data/entities'
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
filterActivePersonCompanyLinks,
|
|
25
|
+
withActiveCustomerPersonCompanyLinkFilter,
|
|
26
|
+
} from '../../../../../lib/personCompanyLinkTable'
|
|
24
27
|
|
|
25
28
|
const paramsSchema = z.object({
|
|
26
29
|
id: z.string().uuid(),
|
|
@@ -187,12 +190,14 @@ export async function GET(req: Request, ctx: { params?: { id?: string } }) {
|
|
|
187
190
|
},
|
|
188
191
|
'customers.people.companiesEnriched.GET',
|
|
189
192
|
)
|
|
190
|
-
const links =
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
const links = filterActivePersonCompanyLinks(
|
|
194
|
+
await findWithDecryption(
|
|
195
|
+
em,
|
|
196
|
+
CustomerPersonCompanyLink,
|
|
197
|
+
linkWhere,
|
|
198
|
+
{ populate: ['company'] },
|
|
199
|
+
entityScope,
|
|
200
|
+
),
|
|
196
201
|
)
|
|
197
202
|
|
|
198
203
|
const companyIds = links.map((link) => (link.company as CustomerEntity).id)
|
|
@@ -29,7 +29,10 @@ import {
|
|
|
29
29
|
createPagedListResponseSchema,
|
|
30
30
|
defaultOkResponseSchema,
|
|
31
31
|
} from '../openapi'
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
filterActivePersonCompanyLinks,
|
|
34
|
+
withActiveCustomerPersonCompanyLinkFilter,
|
|
35
|
+
} from '../../lib/personCompanyLinkTable'
|
|
33
36
|
import { normalizeProfilePayload } from './payload'
|
|
34
37
|
|
|
35
38
|
const rawBodySchema = z.object({}).passthrough()
|
|
@@ -221,12 +224,14 @@ const crud = makeCrudRoute({
|
|
|
221
224
|
{ company: query.excludeLinkedCompanyId },
|
|
222
225
|
'customers.people.GET',
|
|
223
226
|
)
|
|
224
|
-
const links =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
const links = filterActivePersonCompanyLinks(
|
|
228
|
+
await findWithDecryption(
|
|
229
|
+
em,
|
|
230
|
+
CustomerPersonCompanyLink,
|
|
231
|
+
linkWhere,
|
|
232
|
+
{ populate: ['person'] },
|
|
233
|
+
decryptionScope,
|
|
234
|
+
),
|
|
230
235
|
)
|
|
231
236
|
links.forEach((link) => {
|
|
232
237
|
const personId = link.person?.id
|
|
@@ -219,6 +219,27 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
|
|
|
219
219
|
setScheduleDialogOpen(true)
|
|
220
220
|
}, [])
|
|
221
221
|
|
|
222
|
+
const handleAddActivity = React.useCallback((kind: 'meeting' | 'call' | 'task' | 'email') => {
|
|
223
|
+
setScheduleEditData({
|
|
224
|
+
id: '',
|
|
225
|
+
interactionType: kind,
|
|
226
|
+
title: null,
|
|
227
|
+
body: null,
|
|
228
|
+
scheduledAt: null,
|
|
229
|
+
durationMinutes: null,
|
|
230
|
+
location: null,
|
|
231
|
+
allDay: null,
|
|
232
|
+
recurrenceRule: null,
|
|
233
|
+
recurrenceEnd: null,
|
|
234
|
+
participants: null,
|
|
235
|
+
reminderMinutes: null,
|
|
236
|
+
visibility: null,
|
|
237
|
+
linkedEntities: null,
|
|
238
|
+
guestPermissions: null,
|
|
239
|
+
})
|
|
240
|
+
setScheduleDialogOpen(true)
|
|
241
|
+
}, [])
|
|
242
|
+
|
|
222
243
|
// Injected tabs from UMES
|
|
223
244
|
const { widgets: injectedTabWidgets } = useInjectionWidgets('detail:customers.company:tabs', {
|
|
224
245
|
context: injectionContext,
|
|
@@ -473,6 +494,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
|
|
|
473
494
|
plannedActivities={plannedActivities}
|
|
474
495
|
onActivityCreated={handleActivityCreated}
|
|
475
496
|
onScheduleRequested={openNewScheduleDialog}
|
|
497
|
+
onAddActivity={handleAddActivity}
|
|
476
498
|
onMarkDone={handleMarkDone}
|
|
477
499
|
onEditActivity={handleEditActivity}
|
|
478
500
|
onCancelActivity={handleCancelActivity}
|
|
@@ -23,18 +23,17 @@ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuarde
|
|
|
23
23
|
import { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'
|
|
24
24
|
|
|
25
25
|
import { ActivitiesSection } from '../../../../components/detail/ActivitiesSection'
|
|
26
|
+
import { ActivitiesCard } from '../../../../components/detail/ActivitiesCard'
|
|
27
|
+
import type { ActivityKind } from '../../../../components/detail/ActivitiesAddNewMenu'
|
|
26
28
|
import { DealsSection } from '../../../../components/detail/DealsSection'
|
|
27
29
|
import { TasksSection } from '../../../../components/detail/TasksSection'
|
|
28
30
|
import type { TagSummary } from '../../../../components/detail/types'
|
|
29
|
-
import { InlineActivityComposer } from '../../../../components/detail/InlineActivityComposer'
|
|
30
|
-
import { PlannedActivitiesSection } from '../../../../components/detail/PlannedActivitiesSection'
|
|
31
31
|
import { ScheduleActivityDialog, type ScheduleActivityEditData } from '../../../../components/detail/ScheduleActivityDialog'
|
|
32
32
|
import { PersonDetailHeader } from '../../../../components/detail/PersonDetailHeader'
|
|
33
33
|
import { ChangelogTab } from '../../../../components/detail/ChangelogTab'
|
|
34
34
|
import { PersonDetailTabs, resolveLegacyTab, type PersonTabId } from '../../../../components/detail/PersonDetailTabs'
|
|
35
35
|
import { PersonCompaniesSection } from '../../../../components/detail/PersonCompaniesSection'
|
|
36
36
|
import { MobilePersonDetail } from '../../../../components/detail/MobilePersonDetail'
|
|
37
|
-
import { useInteractionMutations } from '../../../../components/detail/hooks/useInteractionMutations'
|
|
38
37
|
import type { TagsSectionController } from '@open-mercato/ui/backend/detail'
|
|
39
38
|
import {
|
|
40
39
|
buildPersonEditPayload,
|
|
@@ -190,11 +189,26 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
|
|
|
190
189
|
[injectionContext, runMutation],
|
|
191
190
|
)
|
|
192
191
|
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
192
|
+
const handleAddActivity = React.useCallback((kind: ActivityKind) => {
|
|
193
|
+
setScheduleEditData({
|
|
194
|
+
id: '',
|
|
195
|
+
interactionType: kind,
|
|
196
|
+
title: null,
|
|
197
|
+
body: null,
|
|
198
|
+
scheduledAt: null,
|
|
199
|
+
durationMinutes: null,
|
|
200
|
+
location: null,
|
|
201
|
+
allDay: null,
|
|
202
|
+
recurrenceRule: null,
|
|
203
|
+
recurrenceEnd: null,
|
|
204
|
+
participants: null,
|
|
205
|
+
reminderMinutes: null,
|
|
206
|
+
visibility: null,
|
|
207
|
+
linkedEntities: null,
|
|
208
|
+
guestPermissions: null,
|
|
209
|
+
})
|
|
210
|
+
setScheduleDialogOpen(true)
|
|
211
|
+
}, [])
|
|
198
212
|
|
|
199
213
|
const handleEditActivity = React.useCallback((activity: { id: string; interactionType?: string; title?: string | null; body?: string | null; scheduledAt?: string | null; [key: string]: unknown }) => {
|
|
200
214
|
const raw = activity as Record<string, unknown>
|
|
@@ -448,20 +462,13 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
|
|
|
448
462
|
if (activeTab === 'activities') {
|
|
449
463
|
return (
|
|
450
464
|
<div className="space-y-4">
|
|
451
|
-
<
|
|
452
|
-
entityType="person"
|
|
465
|
+
<ActivitiesCard
|
|
453
466
|
entityId={personId}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
<PlannedActivitiesSection
|
|
460
|
-
activities={plannedActivities}
|
|
461
|
-
onComplete={handleMarkDone}
|
|
462
|
-
onSchedule={() => { setScheduleEditData(null); setScheduleDialogOpen(true) }}
|
|
463
|
-
onEdit={handleEditActivity}
|
|
464
|
-
onCancel={handleCancelActivity}
|
|
467
|
+
plannedActivities={plannedActivities}
|
|
468
|
+
refreshKey={activityRefreshKey}
|
|
469
|
+
onAddNew={handleAddActivity}
|
|
470
|
+
onEditActivity={handleEditActivity}
|
|
471
|
+
entityCompanyName={data.company?.displayName ?? data.companies?.[0]?.displayName ?? null}
|
|
465
472
|
/>
|
|
466
473
|
<ActivitiesSection
|
|
467
474
|
entityId={personId}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Check, Phone, Mail, Users, CheckSquare } from 'lucide-react'
|
|
5
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
|
|
7
|
+
|
|
8
|
+
export type ActivityKind = 'meeting' | 'call' | 'task' | 'email'
|
|
9
|
+
|
|
10
|
+
interface ActivitiesAddNewMenuProps {
|
|
11
|
+
onSelect: (kind: ActivityKind) => void
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const MENU_ITEMS: ReadonlyArray<{ kind: ActivityKind; icon: React.ComponentType<{ className?: string }>; key: string; fallback: string }> = [
|
|
16
|
+
{ kind: 'meeting', icon: Users, key: 'customers.activities.add.meeting', fallback: 'New meeting' },
|
|
17
|
+
{ kind: 'call', icon: Phone, key: 'customers.activities.add.call', fallback: 'Log call' },
|
|
18
|
+
{ kind: 'task', icon: CheckSquare, key: 'customers.activities.add.task', fallback: 'New task' },
|
|
19
|
+
{ kind: 'email', icon: Mail, key: 'customers.activities.add.email', fallback: 'Compose email' },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
export function ActivitiesAddNewMenu({ onSelect, disabled }: ActivitiesAddNewMenuProps) {
|
|
23
|
+
const t = useT()
|
|
24
|
+
const [open, setOpen] = React.useState(false)
|
|
25
|
+
|
|
26
|
+
const handleSelect = React.useCallback(
|
|
27
|
+
(kind: ActivityKind) => {
|
|
28
|
+
setOpen(false)
|
|
29
|
+
onSelect(kind)
|
|
30
|
+
},
|
|
31
|
+
[onSelect],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
36
|
+
<PopoverTrigger asChild>
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
disabled={disabled}
|
|
40
|
+
aria-label={t('customers.activities.addNew', 'Add new')}
|
|
41
|
+
className="inline-flex items-center gap-1.5 overflow-hidden rounded-md bg-foreground pl-3 pr-3.5 py-2 text-xs font-semibold text-background transition-colors hover:bg-foreground/90 disabled:opacity-60"
|
|
42
|
+
>
|
|
43
|
+
<Check className="size-3.5" />
|
|
44
|
+
{t('customers.activities.addNew', 'Add new')}
|
|
45
|
+
</button>
|
|
46
|
+
</PopoverTrigger>
|
|
47
|
+
<PopoverContent align="end" className="w-[180px] p-1">
|
|
48
|
+
<ul className="flex flex-col">
|
|
49
|
+
{MENU_ITEMS.map(({ kind, icon: Icon, key, fallback }) => (
|
|
50
|
+
<li key={kind}>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
onClick={() => handleSelect(kind)}
|
|
54
|
+
className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-foreground hover:bg-accent/40"
|
|
55
|
+
>
|
|
56
|
+
<Icon className="size-4 text-muted-foreground" />
|
|
57
|
+
{t(key, fallback)}
|
|
58
|
+
</button>
|
|
59
|
+
</li>
|
|
60
|
+
))}
|
|
61
|
+
</ul>
|
|
62
|
+
</PopoverContent>
|
|
63
|
+
</Popover>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default ActivitiesAddNewMenu
|