@open-mercato/core 0.6.4-develop.4178.1.aad9ddaa95 → 0.6.4-develop.4210.1.d412061cfe
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js +14 -14
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
- package/dist/modules/audit_logs/backend/audit-logs/page.js +3 -3
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +3 -7
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityCard.js +2 -2
- package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +34 -49
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/ChangelogEntryRow.js +2 -2
- package/dist/modules/customers/components/detail/ChangelogEntryRow.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailHeader.js +10 -1
- package/dist/modules/customers/components/detail/CompanyDetailHeader.js.map +2 -2
- package/dist/modules/customers/components/detail/DealLinkedEntitiesTab.js +7 -51
- package/dist/modules/customers/components/detail/DealLinkedEntitiesTab.js.map +2 -2
- package/dist/modules/customers/components/detail/DetailTabsLayout.js +1 -1
- package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
- package/dist/modules/customers/components/detail/ManageTagsDialog.js +25 -33
- package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonCard.js +3 -2
- package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailHeader.js +3 -2
- package/dist/modules/customers/components/detail/PersonDetailHeader.js.map +2 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -5
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
- package/dist/modules/customers/components/detail/utils.js +0 -7
- package/dist/modules/customers/components/detail/utils.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +3 -8
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/dictionaries/components/AppearanceSelector.js +3 -4
- package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +17 -17
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +1 -1
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +65 -1
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/planner/lib/deleteAvailabilityRuleSet.js +20 -0
- package/dist/modules/planner/lib/deleteAvailabilityRuleSet.js.map +7 -0
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js +14 -37
- package/dist/modules/sales/api/quotes/accept/route.js.map +3 -3
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +1 -1
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +1 -1
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +6 -2
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +3 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +1 -1
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/translations/components/TranslationDrawerAction.js +27 -65
- package/dist/modules/translations/components/TranslationDrawerAction.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +2 -2
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +54 -92
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +14 -14
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +2 -2
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +3 -3
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -8
- package/src/modules/customers/components/detail/ActivityCard.tsx +2 -4
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +28 -55
- package/src/modules/customers/components/detail/ChangelogEntryRow.tsx +6 -4
- package/src/modules/customers/components/detail/CompanyDetailHeader.tsx +7 -3
- package/src/modules/customers/components/detail/DealLinkedEntitiesTab.tsx +11 -49
- package/src/modules/customers/components/detail/DetailTabsLayout.tsx +1 -1
- package/src/modules/customers/components/detail/ManageTagsDialog.tsx +27 -36
- package/src/modules/customers/components/detail/PersonCard.tsx +3 -4
- package/src/modules/customers/components/detail/PersonDetailHeader.tsx +3 -4
- package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -7
- package/src/modules/customers/components/detail/utils.ts +0 -7
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +4 -9
- package/src/modules/dictionaries/components/AppearanceSelector.tsx +3 -4
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +21 -21
- package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +1 -1
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +62 -0
- package/src/modules/planner/lib/deleteAvailabilityRuleSet.ts +35 -0
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +2 -2
- package/src/modules/sales/api/quotes/accept/route.ts +22 -38
- package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +1 -1
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +1 -1
- package/src/modules/sales/commands/documents.ts +16 -2
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +3 -2
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +1 -1
- package/src/modules/staff/i18n/de.json +5 -0
- package/src/modules/staff/i18n/en.json +5 -0
- package/src/modules/staff/i18n/es.json +5 -0
- package/src/modules/staff/i18n/pl.json +5 -0
- package/src/modules/translations/components/TranslationDrawerAction.tsx +31 -66
- package/src/modules/translations/components/TranslationManager.tsx +2 -2
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +53 -84
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/PersonCard.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Mail, Phone, Star, MoreHorizontal, ArrowUpRight, Unlink } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport type { CompanyPersonSummary } from './CompanyPeopleSection'\nimport { formatDate, formatFallbackLabel, getInitials } from './utils'\n\nconst sourceColorMap: Record<string, string> = {\n linkedin: 'border-status-info-icon text-status-info-icon',\n email: 'border-status-success-icon text-status-success-icon',\n web_form: 'border-status-info-icon text-status-info-icon',\n 'web form': 'border-status-info-icon text-status-info-icon',\n referral: 'border-status-warning-icon text-status-warning-icon',\n customer_referral: 'border-status-warning-icon text-status-warning-icon',\n 'customer referral': 'border-status-warning-icon text-status-warning-icon',\n partner_referral: 'border-status-warning-icon text-status-warning-icon',\n 'partner referral': 'border-status-warning-icon text-status-warning-icon',\n event: 'border-status-info-icon text-status-info-icon',\n 'conference/event': 'border-status-info-icon text-status-info-icon',\n cold_outreach: 'border-status-neutral-icon text-status-neutral-icon',\n 'cold outreach': 'border-status-neutral-icon text-status-neutral-icon',\n facebook: 'border-status-info-icon text-status-info-icon',\n typeform: 'border-status-info-icon text-status-info-icon',\n other: 'border-status-neutral-icon text-status-neutral-icon',\n}\n\nconst temperatureConfig = {\n hot: { filled: 5, dotClassName: 'bg-status-error-icon', labelKey: 'customers.temperature.hot', fallback: 'Hot Client' },\n warm: { filled: 4, dotClassName: 'bg-status-warning-icon', labelKey: 'customers.temperature.warm', fallback: 'High Interest' },\n high: { filled: 4, dotClassName: 'bg-status-warning-icon', labelKey: 'customers.temperature.high', fallback: 'High Interest' },\n neutral: { filled: 3, dotClassName: 'bg-status-info-icon', labelKey: 'customers.temperature.neutral', fallback: 'Engaged' },\n medium: { filled: 3, dotClassName: 'bg-status-info-icon', labelKey: 'customers.temperature.medium', fallback: 'Engaged' },\n cool: { filled: 2, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.cool', fallback: 'Low Activity' },\n low: { filled: 2, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.low', fallback: 'Low Activity' },\n cold: { filled: 1, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.cold', fallback: 'At Risk' },\n} as const\n\nfunction splitSourceTags(source: string | null | undefined): string[] {\n if (typeof source !== 'string') return []\n return source\n .split(/[;,|]/)\n .map((entry) => entry.trim())\n .filter((entry, index, array) => entry.length > 0 && array.indexOf(entry) === index)\n}\n\ninterface PersonCardProps {\n person: CompanyPersonSummary\n isStarred?: boolean\n onToggleStar?: (personId: string) => void\n onUnlink?: (personId: string) => void\n}\n\nexport function PersonCard({ person, isStarred, onToggleStar, onUnlink }: PersonCardProps) {\n const t = useT()\n const sourceTags = React.useMemo(() => splitSourceTags(person.source), [person.source])\n const temperature = React.useMemo(() => {\n const value = typeof person.temperature === 'string' ? person.temperature.trim().toLowerCase() : ''\n return value in temperatureConfig ? temperatureConfig[value as keyof typeof temperatureConfig] : null\n }, [person.temperature])\n const linkedDate = React.useMemo(() => formatDate(person.linkedAt ?? person.createdAt), [person.createdAt, person.linkedAt])\n\n return (\n <div className=\"min-w-0 overflow-hidden rounded-lg border bg-card p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-start gap-3\">\n <div className=\"flex size-11 shrink-0 items-center justify-center rounded-full bg-muted text-sm font-bold text-muted-foreground\">\n {getInitials(person.displayName)}\n </div>\n <div className=\"min-w-0 flex-1 space-y-1\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n <span className=\"min-w-0 break-words text-sm font-bold leading-5 text-foreground\">{person.displayName}</span>\n {onToggleStar && (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onToggleStar(person.id)}\n className=\"h-auto shrink-0 p-0 text-muted-foreground hover:text-status-warning-icon\"\n aria-label={t('customers.people.card.toggleStar', 'Toggle star')}\n >\n <Star className={cn('size-3.5', isStarred && 'fill-status-warning-icon text-status-warning-icon')} />\n </IconButton>\n )}\n </div>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {person.jobTitle && (\n <Badge variant=\"secondary\" className=\"max-w-full truncate px-2 py-0.5 text-xs font-semibold\">\n {person.jobTitle}\n </Badge>\n )}\n {person.status && (\n <Badge variant=\"outline\" className=\"shrink-0 px-2 py-0.5 text-xs font-medium\">\n <span className=\"mr-1 inline-block size-1.5 rounded-full bg-status-success-icon\" />\n {person.status}\n </Badge>\n )}\n </div>\n </div>\n </div>\n <Popover>\n <PopoverTrigger asChild>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"shrink-0\"\n aria-label={t('customers.people.card.more', 'More')}\n >\n <MoreHorizontal className=\"size-3.5\" />\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-36 p-1\">\n {onUnlink && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-start text-xs text-destructive hover:text-destructive\"\n onClick={() => onUnlink(person.id)}\n >\n <Unlink className=\"mr-1.5 size-3\" />\n {t('customers.people.card.unlink', 'Unlink')}\n </Button>\n )}\n </PopoverContent>\n </Popover>\n </div>\n\n <div className=\"mt-3 space-y-2 border-t pt-3 text-xs text-foreground\">\n {person.primaryEmail && (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Mail className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"break-all\">{person.primaryEmail}</span>\n </div>\n )}\n {person.primaryPhone && (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Phone className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"break-all\">{person.primaryPhone}</span>\n </div>\n )}\n {(person.lifecycleStage || linkedDate) && (\n <div className=\"flex min-w-0 flex-wrap items-center gap-1 text-xs text-muted-foreground\">\n <span className=\"truncate\">{person.lifecycleStage ?? t('customers.people.card.defaultStage', 'customer')}</span>\n {linkedDate ? (\n <>\n <span aria-hidden>·</span>\n <span className=\"truncate\">{t('customers.people.card.linkedOn', 'Linked {{date}}', { date: linkedDate })}</span>\n </>\n ) : null}\n </div>\n )}\n </div>\n\n {(temperature || sourceTags.length > 0) && (\n <div className=\"mt-3 flex flex-col gap-3 border-t pt-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0 flex-1 space-y-1\">\n {sourceTags.length > 0 ? (\n <>\n <p className=\"text-overline font-bold uppercase tracking-wide text-muted-foreground\">\n {t('customers.people.card.source', 'Source')}\n </p>\n <div className=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n {sourceTags.map((sourceTag) => {\n const normalized = sourceTag.toLowerCase().replace(/\\s+/g, '_')\n return (\n <Badge\n key={sourceTag}\n variant=\"outline\"\n className={cn(\n 'max-w-full truncate px-2 py-0.5 text-xs font-semibold',\n sourceColorMap[normalized] ?? sourceColorMap[sourceTag.toLowerCase()] ?? 'border-border text-muted-foreground',\n )}\n >\n {formatFallbackLabel(sourceTag)}\n </Badge>\n )\n })}\n </div>\n </>\n ) : null}\n </div>\n {temperature ? (\n <div className=\"space-y-1 sm:ml-auto sm:text-right\">\n <div className=\"flex items-center gap-1 text-overline font-bold text-muted-foreground sm:justify-end\">\n <span>{t(temperature.labelKey, temperature.fallback)}</span>\n </div>\n <div className=\"flex items-center gap-1 sm:justify-end\">\n {Array.from({ length: 5 }).map((_, index) => (\n <span\n key={index}\n className={cn(\n 'inline-block size-2 rounded-full border border-border/70',\n index < temperature.filled ? temperature.dotClassName : 'bg-muted',\n )}\n />\n ))}\n </div>\n </div>\n ) : null}\n </div>\n )}\n\n <div className=\"mt-3 flex flex-col gap-2 border-t pt-3 sm:flex-row sm:flex-wrap\">\n <Button\n asChild\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-full text-xs font-semibold sm:min-w-[9rem] sm:flex-1\"\n >\n <Link href={`/backend/customers/people-v2/${person.id}`}>\n <ArrowUpRight className=\"mr-1 size-3\" />\n {t('customers.people.card.open', 'Open person')}\n </Link>\n </Button>\n {onUnlink && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-full border-destructive/30 text-xs font-semibold text-destructive hover:bg-destructive/10 sm:min-w-[7rem] sm:flex-none\"\n onClick={() => onUnlink(person.id)}\n >\n {t('customers.people.card.unlink', 'Unlink')}\n </Button>\n )}\n </div>\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Mail, Phone, Star, MoreHorizontal, ArrowUpRight, Unlink } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport type { CompanyPersonSummary } from './CompanyPeopleSection'\nimport { formatDate, formatFallbackLabel } from './utils'\n\nconst sourceColorMap: Record<string, string> = {\n linkedin: 'border-status-info-icon text-status-info-icon',\n email: 'border-status-success-icon text-status-success-icon',\n web_form: 'border-status-info-icon text-status-info-icon',\n 'web form': 'border-status-info-icon text-status-info-icon',\n referral: 'border-status-warning-icon text-status-warning-icon',\n customer_referral: 'border-status-warning-icon text-status-warning-icon',\n 'customer referral': 'border-status-warning-icon text-status-warning-icon',\n partner_referral: 'border-status-warning-icon text-status-warning-icon',\n 'partner referral': 'border-status-warning-icon text-status-warning-icon',\n event: 'border-status-info-icon text-status-info-icon',\n 'conference/event': 'border-status-info-icon text-status-info-icon',\n cold_outreach: 'border-status-neutral-icon text-status-neutral-icon',\n 'cold outreach': 'border-status-neutral-icon text-status-neutral-icon',\n facebook: 'border-status-info-icon text-status-info-icon',\n typeform: 'border-status-info-icon text-status-info-icon',\n other: 'border-status-neutral-icon text-status-neutral-icon',\n}\n\nconst temperatureConfig = {\n hot: { filled: 5, dotClassName: 'bg-status-error-icon', labelKey: 'customers.temperature.hot', fallback: 'Hot Client' },\n warm: { filled: 4, dotClassName: 'bg-status-warning-icon', labelKey: 'customers.temperature.warm', fallback: 'High Interest' },\n high: { filled: 4, dotClassName: 'bg-status-warning-icon', labelKey: 'customers.temperature.high', fallback: 'High Interest' },\n neutral: { filled: 3, dotClassName: 'bg-status-info-icon', labelKey: 'customers.temperature.neutral', fallback: 'Engaged' },\n medium: { filled: 3, dotClassName: 'bg-status-info-icon', labelKey: 'customers.temperature.medium', fallback: 'Engaged' },\n cool: { filled: 2, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.cool', fallback: 'Low Activity' },\n low: { filled: 2, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.low', fallback: 'Low Activity' },\n cold: { filled: 1, dotClassName: 'bg-status-neutral-icon', labelKey: 'customers.temperature.cold', fallback: 'At Risk' },\n} as const\n\nfunction splitSourceTags(source: string | null | undefined): string[] {\n if (typeof source !== 'string') return []\n return source\n .split(/[;,|]/)\n .map((entry) => entry.trim())\n .filter((entry, index, array) => entry.length > 0 && array.indexOf(entry) === index)\n}\n\ninterface PersonCardProps {\n person: CompanyPersonSummary\n isStarred?: boolean\n onToggleStar?: (personId: string) => void\n onUnlink?: (personId: string) => void\n}\n\nexport function PersonCard({ person, isStarred, onToggleStar, onUnlink }: PersonCardProps) {\n const t = useT()\n const sourceTags = React.useMemo(() => splitSourceTags(person.source), [person.source])\n const temperature = React.useMemo(() => {\n const value = typeof person.temperature === 'string' ? person.temperature.trim().toLowerCase() : ''\n return value in temperatureConfig ? temperatureConfig[value as keyof typeof temperatureConfig] : null\n }, [person.temperature])\n const linkedDate = React.useMemo(() => formatDate(person.linkedAt ?? person.createdAt), [person.createdAt, person.linkedAt])\n\n return (\n <div className=\"min-w-0 overflow-hidden rounded-lg border bg-card p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-start gap-3\">\n <Avatar label={person.displayName} size=\"lg\" variant=\"monochrome\" />\n <div className=\"min-w-0 flex-1 space-y-1\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n <span className=\"min-w-0 break-words text-sm font-bold leading-5 text-foreground\">{person.displayName}</span>\n {onToggleStar && (\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onToggleStar(person.id)}\n className=\"h-auto shrink-0 p-0 text-muted-foreground hover:text-status-warning-icon\"\n aria-label={t('customers.people.card.toggleStar', 'Toggle star')}\n >\n <Star className={cn('size-3.5', isStarred && 'fill-status-warning-icon text-status-warning-icon')} />\n </IconButton>\n )}\n </div>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {person.jobTitle && (\n <Badge variant=\"secondary\" className=\"max-w-full truncate px-2 py-0.5 text-xs font-semibold\">\n {person.jobTitle}\n </Badge>\n )}\n {person.status && (\n <Badge variant=\"outline\" className=\"shrink-0 px-2 py-0.5 text-xs font-medium\">\n <span className=\"mr-1 inline-block size-1.5 rounded-full bg-status-success-icon\" />\n {person.status}\n </Badge>\n )}\n </div>\n </div>\n </div>\n <Popover>\n <PopoverTrigger asChild>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"shrink-0\"\n aria-label={t('customers.people.card.more', 'More')}\n >\n <MoreHorizontal className=\"size-3.5\" />\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-36 p-1\">\n {onUnlink && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-start text-xs text-destructive hover:text-destructive\"\n onClick={() => onUnlink(person.id)}\n >\n <Unlink className=\"mr-1.5 size-3\" />\n {t('customers.people.card.unlink', 'Unlink')}\n </Button>\n )}\n </PopoverContent>\n </Popover>\n </div>\n\n <div className=\"mt-3 space-y-2 border-t pt-3 text-xs text-foreground\">\n {person.primaryEmail && (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Mail className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"break-all\">{person.primaryEmail}</span>\n </div>\n )}\n {person.primaryPhone && (\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Phone className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"break-all\">{person.primaryPhone}</span>\n </div>\n )}\n {(person.lifecycleStage || linkedDate) && (\n <div className=\"flex min-w-0 flex-wrap items-center gap-1 text-xs text-muted-foreground\">\n <span className=\"truncate\">{person.lifecycleStage ?? t('customers.people.card.defaultStage', 'customer')}</span>\n {linkedDate ? (\n <>\n <span aria-hidden>·</span>\n <span className=\"truncate\">{t('customers.people.card.linkedOn', 'Linked {{date}}', { date: linkedDate })}</span>\n </>\n ) : null}\n </div>\n )}\n </div>\n\n {(temperature || sourceTags.length > 0) && (\n <div className=\"mt-3 flex flex-col gap-3 border-t pt-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0 flex-1 space-y-1\">\n {sourceTags.length > 0 ? (\n <>\n <p className=\"text-overline font-bold uppercase tracking-wide text-muted-foreground\">\n {t('customers.people.card.source', 'Source')}\n </p>\n <div className=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n {sourceTags.map((sourceTag) => {\n const normalized = sourceTag.toLowerCase().replace(/\\s+/g, '_')\n return (\n <Badge\n key={sourceTag}\n variant=\"outline\"\n className={cn(\n 'max-w-full truncate px-2 py-0.5 text-xs font-semibold',\n sourceColorMap[normalized] ?? sourceColorMap[sourceTag.toLowerCase()] ?? 'border-border text-muted-foreground',\n )}\n >\n {formatFallbackLabel(sourceTag)}\n </Badge>\n )\n })}\n </div>\n </>\n ) : null}\n </div>\n {temperature ? (\n <div className=\"space-y-1 sm:ml-auto sm:text-right\">\n <div className=\"flex items-center gap-1 text-overline font-bold text-muted-foreground sm:justify-end\">\n <span>{t(temperature.labelKey, temperature.fallback)}</span>\n </div>\n <div className=\"flex items-center gap-1 sm:justify-end\">\n {Array.from({ length: 5 }).map((_, index) => (\n <span\n key={index}\n className={cn(\n 'inline-block size-2 rounded-full border border-border/70',\n index < temperature.filled ? temperature.dotClassName : 'bg-muted',\n )}\n />\n ))}\n </div>\n </div>\n ) : null}\n </div>\n )}\n\n <div className=\"mt-3 flex flex-col gap-2 border-t pt-3 sm:flex-row sm:flex-wrap\">\n <Button\n asChild\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-full text-xs font-semibold sm:min-w-[9rem] sm:flex-1\"\n >\n <Link href={`/backend/customers/people-v2/${person.id}`}>\n <ArrowUpRight className=\"mr-1 size-3\" />\n {t('customers.people.card.open', 'Open person')}\n </Link>\n </Button>\n {onUnlink && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-full border-destructive/30 text-xs font-semibold text-destructive hover:bg-destructive/10 sm:min-w-[7rem] sm:flex-none\"\n onClick={() => onUnlink(person.id)}\n >\n {t('customers.people.card.unlink', 'Unlink')}\n </Button>\n )}\n </div>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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";
|
|
@@ -14,7 +15,7 @@ import { ObjectHistoryButton } from "./ObjectHistoryButton.js";
|
|
|
14
15
|
import { PersonTagsDialog } from "./PersonTagsDialog.js";
|
|
15
16
|
import { useCustomerDictionary, invalidateCustomerDictionary } from "./hooks/useCustomerDictionary.js";
|
|
16
17
|
import { renderDictionaryIcon } from "../../../dictionaries/components/dictionaryAppearance.js";
|
|
17
|
-
import {
|
|
18
|
+
import { formatFallbackLabel } from "./utils.js";
|
|
18
19
|
const HEADER_ICON_BUTTON_CLASS = "size-8 rounded-md";
|
|
19
20
|
function DictionaryBadge({ value, map, categoryIcon, className }) {
|
|
20
21
|
const entry = map?.[value];
|
|
@@ -80,7 +81,7 @@ function PersonDetailHeader({
|
|
|
80
81
|
const { data: renewalQuarterDict } = useCustomerDictionary("renewal-quarters", 0, personOrgId);
|
|
81
82
|
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card px-6 py-5", children: [
|
|
82
83
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:gap-5", children: [
|
|
83
|
-
/* @__PURE__ */ jsx(
|
|
84
|
+
/* @__PURE__ */ jsx(Avatar, { label: displayName, size: "xl", variant: "monochrome" }),
|
|
84
85
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
85
86
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
86
87
|
/* @__PURE__ */ jsx("h1", { className: "truncate text-2xl font-bold text-foreground", children: displayName }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/PersonDetailHeader.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Phone, Mail, Building2, Trash2, Pencil } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { ObjectHistoryButton } from './ObjectHistoryButton'\nimport { PersonTagsDialog } from './PersonTagsDialog'\nimport { useCustomerDictionary, invalidateCustomerDictionary } from './hooks/useCustomerDictionary'\nimport { renderDictionaryIcon } from '../../../dictionaries/components/dictionaryAppearance'\nimport type { TagSummary } from './types'\nimport type { TagsSectionController } from '@open-mercato/ui/backend/detail'\nimport type { PersonOverview } from '../formConfig'\nimport type { CustomerDictionaryMap } from '@open-mercato/core/modules/customers/lib/dictionaries'\nimport { getInitials, formatFallbackLabel } from './utils'\n\nconst HEADER_ICON_BUTTON_CLASS = 'size-8 rounded-md'\n\ntype PersonDetailHeaderProps = {\n data: PersonOverview\n onTagsChange: (tags: TagSummary[]) => void\n tagsSectionControllerRef: React.RefObject<TagsSectionController | null>\n onSave: () => void\n onDelete: () => Promise<void>\n isDirty: boolean\n isSaving: boolean\n /** Callback to focus a specific field in the Zone 1 CrudForm by field name. */\n onFocusField?: (fieldName: string) => void\n /**\n * @deprecated Kept for backward compatibility. The \"+ Link company\" header CTA was removed;\n * company linking now happens exclusively through the Zone 2 Companies tab via\n * `PersonCompaniesSection`. This prop is a no-op and will be removed in a future major release.\n */\n onOpenCompaniesTab?: () => void\n /** Callback to reload person data after tags dialog save. */\n onDataReload?: () => void\n}\n\nfunction DictionaryBadge({ value, map, categoryIcon, className }: { value: string; map: CustomerDictionaryMap | undefined; categoryIcon?: React.ReactNode; className?: string }) {\n const entry = map?.[value]\n const color = entry?.color ?? null\n const icon = entry?.icon ?? null\n const label = entry?.label ?? formatFallbackLabel(value)\n const colorStyle: React.CSSProperties | undefined = color\n ? { color, borderColor: color, backgroundColor: `${color}1A` }\n : undefined\n return (\n <Badge\n variant=\"outline\"\n className={cn(\n 'rounded-sm gap-1.5 text-xs font-medium',\n className,\n )}\n style={colorStyle}\n >\n {icon ? renderDictionaryIcon(icon, 'size-2.5') : categoryIcon ?? null}\n {label}\n </Badge>\n )\n}\n\n/** Renders a tag badge with color-based text/border/background from TagSummary.color. */\nfunction TagBadge({ tag }: { tag: TagSummary }) {\n const colorStyle: React.CSSProperties | undefined = tag.color\n ? { color: tag.color, borderColor: tag.color, backgroundColor: `${tag.color}1A` }\n : undefined\n return (\n <Badge variant=\"outline\" className=\"rounded-sm gap-1.5 text-xs font-medium\" style={colorStyle}>\n {tag.label}\n </Badge>\n )\n}\n\nexport function PersonDetailHeader({\n data,\n onTagsChange,\n tagsSectionControllerRef,\n onSave,\n onDelete,\n isDirty,\n isSaving,\n onFocusField,\n onOpenCompaniesTab,\n onDataReload,\n}: PersonDetailHeaderProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const [manageTagsOpen, setManageTagsOpen] = React.useState(false)\n const person = data.person\n const profile = data.profile\n const displayName = person.displayName || t('customers.people.detail.untitled', 'Untitled')\n\n const jobTitle = profile?.jobTitle ?? null\n const linkedCompanies = React.useMemo(() => {\n const items = Array.isArray(data.companies) && data.companies.length > 0\n ? data.companies\n : data.company\n ? [{ ...data.company, isPrimary: Boolean(data.isPrimary) }]\n : []\n return items\n }, [data.companies, data.company, data.isPrimary])\n const visibleCompanies = React.useMemo(() => linkedCompanies.slice(0, 3), [linkedCompanies])\n const hiddenCompaniesCount = Math.max(0, linkedCompanies.length - visibleCompanies.length)\n const visibleTags = React.useMemo(() => data.tags.slice(0, 6), [data.tags])\n const hiddenTagsCount = Math.max(0, data.tags.length - visibleTags.length)\n const primaryCompany = linkedCompanies.find((entry) => entry.isPrimary) ?? linkedCompanies[0] ?? null\n const companyName = primaryCompany?.displayName ?? null\n const companyId = primaryCompany?.id ?? profile?.companyEntityId ?? null\n\n // Fetch dictionary maps for colored badge rendering (scoped to person's organization)\n const personOrgId = person.organizationId ?? null\n const { data: statusDict } = useCustomerDictionary('statuses', 0, personOrgId)\n const { data: lifecycleDict } = useCustomerDictionary('lifecycle-stages', 0, personOrgId)\n const { data: sourceDict } = useCustomerDictionary('sources', 0, personOrgId)\n const { data: temperatureDict } = useCustomerDictionary('temperature', 0, personOrgId)\n const { data: renewalQuarterDict } = useCustomerDictionary('renewal-quarters', 0, personOrgId)\n\n return (\n <div className=\"rounded-lg border bg-card px-6 py-5\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:gap-5\">\n {/* Avatar */}\n <div className=\"flex size-18 shrink-0 items-center justify-center rounded-full bg-muted text-xl font-bold text-muted-foreground\">\n {getInitials(displayName)}\n </div>\n\n {/* Person info */}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <h1 className=\"truncate text-2xl font-bold text-foreground\">{displayName}</h1>\n {data.isPrimary && (\n <span className=\"shrink-0 rounded-sm bg-status-warning-bg px-1.5 py-0.5 text-overline font-bold text-status-warning-text\">\n {t('customers.people.detail.header.primary', 'PRIMARY')}\n </span>\n )}\n </div>\n\n {/* Subtitle: job title + company link */}\n {(jobTitle || companyName) && (\n <p className=\"mt-0.5 text-sm text-muted-foreground\">\n {jobTitle}\n {jobTitle && companyName && ` ${t('customers.people.detail.header.at', 'at')} `}\n {companyName && companyId && (\n <Link href={`/backend/customers/companies-v2/${companyId}`} className=\"text-primary hover:underline\">\n {companyName}\n </Link>\n )}\n {companyName && !companyId && companyName}\n </p>\n )}\n\n {/* Contact row */}\n <div className=\"mt-1.5 flex flex-wrap items-center gap-x-5 gap-y-1 text-sm text-muted-foreground\">\n {person.primaryPhone && (\n <span className=\"inline-flex items-center gap-1.5\">\n <Phone className=\"size-3.5\" />\n <a href={`tel:${person.primaryPhone}`} className=\"hover:text-foreground\">{person.primaryPhone}</a>\n </span>\n )}\n {person.primaryEmail && (\n <span className=\"inline-flex items-center gap-1.5\">\n <Mail className=\"size-3.5\" />\n <a href={`mailto:${person.primaryEmail}`} className=\"hover:text-foreground\">{person.primaryEmail}</a>\n </span>\n )}\n </div>\n\n {/* Company chips (annotation 1a) */}\n {linkedCompanies.length > 0 && (\n <div className=\"mt-1.5 flex flex-wrap items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">\n <Building2 className=\"mr-1 inline size-3.5\" />\n {t('customers.people.detail.header.companies', 'Companies')} ({linkedCompanies.length}):\n </span>\n {visibleCompanies.map((company) => (\n <Link\n key={company.id}\n href={`/backend/customers/companies-v2/${company.id}`}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-sm border px-2 py-0.5 text-xs font-semibold transition-colors hover:bg-status-info-bg',\n company.isPrimary\n ? 'border-status-info-border bg-status-info-bg text-status-info-text'\n : 'border-border bg-background text-foreground',\n )}\n >\n <Building2 className=\"size-3\" />\n {company.displayName}\n {company.isPrimary ? (\n <span className=\"rounded-sm bg-status-info-icon px-1 py-px text-overline font-bold text-white\">\n {t('customers.people.detail.header.primary', 'PRIMARY')}\n </span>\n ) : null}\n </Link>\n ))}\n {hiddenCompaniesCount > 0 ? (\n <Badge variant=\"outline\" className=\"rounded-sm text-xs font-semibold\">\n +{hiddenCompaniesCount} {t('customers.people.detail.header.more', 'more')}\n </Badge>\n ) : null}\n </div>\n )}\n\n {/* Status badges + inline tags + manage tags */}\n <div className=\"mt-2.5 flex flex-wrap items-center gap-2\">\n {person.status && (\n <DictionaryBadge value={person.status} map={statusDict?.map} />\n )}\n {person.lifecycleStage && (\n <DictionaryBadge value={person.lifecycleStage} map={lifecycleDict?.map} />\n )}\n {person.source && (\n <DictionaryBadge value={person.source} map={sourceDict?.map} />\n )}\n {person.temperature && (\n <DictionaryBadge value={person.temperature} map={temperatureDict?.map} />\n )}\n {person.renewalQuarter && (\n <DictionaryBadge value={person.renewalQuarter} map={renewalQuarterDict?.map} />\n )}\n {/* Inline tag pills */}\n {visibleTags.map((tag) => (\n <TagBadge key={tag.id ?? tag.label} tag={tag} />\n ))}\n {hiddenTagsCount > 0 ? (\n <Badge variant=\"outline\" className=\"rounded-sm gap-1.5 text-xs font-medium\">\n +{hiddenTagsCount} {t('customers.people.detail.header.more', 'more')}\n </Badge>\n ) : null}\n {/* Manage tags \u2014 opens dialog directly */}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto rounded-sm px-2 py-1 text-xs font-medium text-muted-foreground hover:text-foreground\"\n onClick={() => setManageTagsOpen(true)}\n >\n <Pencil className=\"mr-1 size-3\" />\n {t('customers.people.detail.actions.manageTags', 'Edit tags')}\n </Button>\n </div>\n </div>\n\n {/* Right side: actions */}\n <div className=\"flex w-full shrink-0 items-center justify-start gap-2 sm:w-auto sm:justify-end\">\n <SendObjectMessageDialog\n object={{\n entityModule: 'customers',\n entityType: 'person',\n entityId: person.id,\n previewData: {\n title: displayName,\n subtitle: person.primaryEmail ?? companyName ?? undefined,\n },\n }}\n viewHref={`/backend/customers/people-v2/${person.id}`}\n buttonVariant=\"outline\"\n buttonSize=\"icon\"\n buttonClassName={HEADER_ICON_BUTTON_CLASS}\n buttonLabel={t('customers.people.detail.actions.sendMessage', 'Send message')}\n />\n <ObjectHistoryButton\n resourceKind=\"customers.person\"\n resourceId={person.id}\n organizationId={person.organizationId ?? undefined}\n />\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n type=\"button\"\n aria-label={t('customers.people.detail.actions.delete', 'Delete')}\n onClick={() => {\n void onDelete()\n }}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={onSave}\n disabled={!isDirty || isSaving}\n >\n {t('customers.people.detail.actions.save', 'Save')}\n </Button>\n </div>\n </div>\n <PersonTagsDialog\n open={manageTagsOpen}\n onClose={() => setManageTagsOpen(false)}\n personId={person.id}\n personOrganizationId={person.organizationId ?? null}\n personData={{\n status: person.status,\n lifecycleStage: person.lifecycleStage,\n source: person.source,\n temperature: person.temperature,\n renewalQuarter: person.renewalQuarter,\n jobTitle: data.profile?.jobTitle ?? null,\n customFields: data.customFields,\n tags: data.tags,\n }}\n onSaved={() => {\n // Invalidate dictionary caches so header badges pick up fresh colors\n void invalidateCustomerDictionary(queryClient, 'statuses')\n void invalidateCustomerDictionary(queryClient, 'lifecycle-stages')\n void invalidateCustomerDictionary(queryClient, 'sources')\n void invalidateCustomerDictionary(queryClient, 'temperature')\n void invalidateCustomerDictionary(queryClient, 'renewal-quarters')\n onDataReload?.()\n }}\n />\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Phone, Mail, Building2, Trash2, Pencil } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { ObjectHistoryButton } from './ObjectHistoryButton'\nimport { PersonTagsDialog } from './PersonTagsDialog'\nimport { useCustomerDictionary, invalidateCustomerDictionary } from './hooks/useCustomerDictionary'\nimport { renderDictionaryIcon } from '../../../dictionaries/components/dictionaryAppearance'\nimport type { TagSummary } from './types'\nimport type { TagsSectionController } from '@open-mercato/ui/backend/detail'\nimport type { PersonOverview } from '../formConfig'\nimport type { CustomerDictionaryMap } from '@open-mercato/core/modules/customers/lib/dictionaries'\nimport { formatFallbackLabel } from './utils'\n\nconst HEADER_ICON_BUTTON_CLASS = 'size-8 rounded-md'\n\ntype PersonDetailHeaderProps = {\n data: PersonOverview\n onTagsChange: (tags: TagSummary[]) => void\n tagsSectionControllerRef: React.RefObject<TagsSectionController | null>\n onSave: () => void\n onDelete: () => Promise<void>\n isDirty: boolean\n isSaving: boolean\n /** Callback to focus a specific field in the Zone 1 CrudForm by field name. */\n onFocusField?: (fieldName: string) => void\n /**\n * @deprecated Kept for backward compatibility. The \"+ Link company\" header CTA was removed;\n * company linking now happens exclusively through the Zone 2 Companies tab via\n * `PersonCompaniesSection`. This prop is a no-op and will be removed in a future major release.\n */\n onOpenCompaniesTab?: () => void\n /** Callback to reload person data after tags dialog save. */\n onDataReload?: () => void\n}\n\nfunction DictionaryBadge({ value, map, categoryIcon, className }: { value: string; map: CustomerDictionaryMap | undefined; categoryIcon?: React.ReactNode; className?: string }) {\n const entry = map?.[value]\n const color = entry?.color ?? null\n const icon = entry?.icon ?? null\n const label = entry?.label ?? formatFallbackLabel(value)\n const colorStyle: React.CSSProperties | undefined = color\n ? { color, borderColor: color, backgroundColor: `${color}1A` }\n : undefined\n return (\n <Badge\n variant=\"outline\"\n className={cn(\n 'rounded-sm gap-1.5 text-xs font-medium',\n className,\n )}\n style={colorStyle}\n >\n {icon ? renderDictionaryIcon(icon, 'size-2.5') : categoryIcon ?? null}\n {label}\n </Badge>\n )\n}\n\n/** Renders a tag badge with color-based text/border/background from TagSummary.color. */\nfunction TagBadge({ tag }: { tag: TagSummary }) {\n const colorStyle: React.CSSProperties | undefined = tag.color\n ? { color: tag.color, borderColor: tag.color, backgroundColor: `${tag.color}1A` }\n : undefined\n return (\n <Badge variant=\"outline\" className=\"rounded-sm gap-1.5 text-xs font-medium\" style={colorStyle}>\n {tag.label}\n </Badge>\n )\n}\n\nexport function PersonDetailHeader({\n data,\n onTagsChange,\n tagsSectionControllerRef,\n onSave,\n onDelete,\n isDirty,\n isSaving,\n onFocusField,\n onOpenCompaniesTab,\n onDataReload,\n}: PersonDetailHeaderProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const [manageTagsOpen, setManageTagsOpen] = React.useState(false)\n const person = data.person\n const profile = data.profile\n const displayName = person.displayName || t('customers.people.detail.untitled', 'Untitled')\n\n const jobTitle = profile?.jobTitle ?? null\n const linkedCompanies = React.useMemo(() => {\n const items = Array.isArray(data.companies) && data.companies.length > 0\n ? data.companies\n : data.company\n ? [{ ...data.company, isPrimary: Boolean(data.isPrimary) }]\n : []\n return items\n }, [data.companies, data.company, data.isPrimary])\n const visibleCompanies = React.useMemo(() => linkedCompanies.slice(0, 3), [linkedCompanies])\n const hiddenCompaniesCount = Math.max(0, linkedCompanies.length - visibleCompanies.length)\n const visibleTags = React.useMemo(() => data.tags.slice(0, 6), [data.tags])\n const hiddenTagsCount = Math.max(0, data.tags.length - visibleTags.length)\n const primaryCompany = linkedCompanies.find((entry) => entry.isPrimary) ?? linkedCompanies[0] ?? null\n const companyName = primaryCompany?.displayName ?? null\n const companyId = primaryCompany?.id ?? profile?.companyEntityId ?? null\n\n // Fetch dictionary maps for colored badge rendering (scoped to person's organization)\n const personOrgId = person.organizationId ?? null\n const { data: statusDict } = useCustomerDictionary('statuses', 0, personOrgId)\n const { data: lifecycleDict } = useCustomerDictionary('lifecycle-stages', 0, personOrgId)\n const { data: sourceDict } = useCustomerDictionary('sources', 0, personOrgId)\n const { data: temperatureDict } = useCustomerDictionary('temperature', 0, personOrgId)\n const { data: renewalQuarterDict } = useCustomerDictionary('renewal-quarters', 0, personOrgId)\n\n return (\n <div className=\"rounded-lg border bg-card px-6 py-5\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:gap-5\">\n {/* Avatar */}\n <Avatar label={displayName} size=\"xl\" variant=\"monochrome\" />\n\n {/* Person info */}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <h1 className=\"truncate text-2xl font-bold text-foreground\">{displayName}</h1>\n {data.isPrimary && (\n <span className=\"shrink-0 rounded-sm bg-status-warning-bg px-1.5 py-0.5 text-overline font-bold text-status-warning-text\">\n {t('customers.people.detail.header.primary', 'PRIMARY')}\n </span>\n )}\n </div>\n\n {/* Subtitle: job title + company link */}\n {(jobTitle || companyName) && (\n <p className=\"mt-0.5 text-sm text-muted-foreground\">\n {jobTitle}\n {jobTitle && companyName && ` ${t('customers.people.detail.header.at', 'at')} `}\n {companyName && companyId && (\n <Link href={`/backend/customers/companies-v2/${companyId}`} className=\"text-primary hover:underline\">\n {companyName}\n </Link>\n )}\n {companyName && !companyId && companyName}\n </p>\n )}\n\n {/* Contact row */}\n <div className=\"mt-1.5 flex flex-wrap items-center gap-x-5 gap-y-1 text-sm text-muted-foreground\">\n {person.primaryPhone && (\n <span className=\"inline-flex items-center gap-1.5\">\n <Phone className=\"size-3.5\" />\n <a href={`tel:${person.primaryPhone}`} className=\"hover:text-foreground\">{person.primaryPhone}</a>\n </span>\n )}\n {person.primaryEmail && (\n <span className=\"inline-flex items-center gap-1.5\">\n <Mail className=\"size-3.5\" />\n <a href={`mailto:${person.primaryEmail}`} className=\"hover:text-foreground\">{person.primaryEmail}</a>\n </span>\n )}\n </div>\n\n {/* Company chips (annotation 1a) */}\n {linkedCompanies.length > 0 && (\n <div className=\"mt-1.5 flex flex-wrap items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">\n <Building2 className=\"mr-1 inline size-3.5\" />\n {t('customers.people.detail.header.companies', 'Companies')} ({linkedCompanies.length}):\n </span>\n {visibleCompanies.map((company) => (\n <Link\n key={company.id}\n href={`/backend/customers/companies-v2/${company.id}`}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-sm border px-2 py-0.5 text-xs font-semibold transition-colors hover:bg-status-info-bg',\n company.isPrimary\n ? 'border-status-info-border bg-status-info-bg text-status-info-text'\n : 'border-border bg-background text-foreground',\n )}\n >\n <Building2 className=\"size-3\" />\n {company.displayName}\n {company.isPrimary ? (\n <span className=\"rounded-sm bg-status-info-icon px-1 py-px text-overline font-bold text-white\">\n {t('customers.people.detail.header.primary', 'PRIMARY')}\n </span>\n ) : null}\n </Link>\n ))}\n {hiddenCompaniesCount > 0 ? (\n <Badge variant=\"outline\" className=\"rounded-sm text-xs font-semibold\">\n +{hiddenCompaniesCount} {t('customers.people.detail.header.more', 'more')}\n </Badge>\n ) : null}\n </div>\n )}\n\n {/* Status badges + inline tags + manage tags */}\n <div className=\"mt-2.5 flex flex-wrap items-center gap-2\">\n {person.status && (\n <DictionaryBadge value={person.status} map={statusDict?.map} />\n )}\n {person.lifecycleStage && (\n <DictionaryBadge value={person.lifecycleStage} map={lifecycleDict?.map} />\n )}\n {person.source && (\n <DictionaryBadge value={person.source} map={sourceDict?.map} />\n )}\n {person.temperature && (\n <DictionaryBadge value={person.temperature} map={temperatureDict?.map} />\n )}\n {person.renewalQuarter && (\n <DictionaryBadge value={person.renewalQuarter} map={renewalQuarterDict?.map} />\n )}\n {/* Inline tag pills */}\n {visibleTags.map((tag) => (\n <TagBadge key={tag.id ?? tag.label} tag={tag} />\n ))}\n {hiddenTagsCount > 0 ? (\n <Badge variant=\"outline\" className=\"rounded-sm gap-1.5 text-xs font-medium\">\n +{hiddenTagsCount} {t('customers.people.detail.header.more', 'more')}\n </Badge>\n ) : null}\n {/* Manage tags \u2014 opens dialog directly */}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-auto rounded-sm px-2 py-1 text-xs font-medium text-muted-foreground hover:text-foreground\"\n onClick={() => setManageTagsOpen(true)}\n >\n <Pencil className=\"mr-1 size-3\" />\n {t('customers.people.detail.actions.manageTags', 'Edit tags')}\n </Button>\n </div>\n </div>\n\n {/* Right side: actions */}\n <div className=\"flex w-full shrink-0 items-center justify-start gap-2 sm:w-auto sm:justify-end\">\n <SendObjectMessageDialog\n object={{\n entityModule: 'customers',\n entityType: 'person',\n entityId: person.id,\n previewData: {\n title: displayName,\n subtitle: person.primaryEmail ?? companyName ?? undefined,\n },\n }}\n viewHref={`/backend/customers/people-v2/${person.id}`}\n buttonVariant=\"outline\"\n buttonSize=\"icon\"\n buttonClassName={HEADER_ICON_BUTTON_CLASS}\n buttonLabel={t('customers.people.detail.actions.sendMessage', 'Send message')}\n />\n <ObjectHistoryButton\n resourceKind=\"customers.person\"\n resourceId={person.id}\n organizationId={person.organizationId ?? undefined}\n />\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n type=\"button\"\n aria-label={t('customers.people.detail.actions.delete', 'Delete')}\n onClick={() => {\n void onDelete()\n }}\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={onSave}\n disabled={!isDirty || isSaving}\n >\n {t('customers.people.detail.actions.save', 'Save')}\n </Button>\n </div>\n </div>\n <PersonTagsDialog\n open={manageTagsOpen}\n onClose={() => setManageTagsOpen(false)}\n personId={person.id}\n personOrganizationId={person.organizationId ?? null}\n personData={{\n status: person.status,\n lifecycleStage: person.lifecycleStage,\n source: person.source,\n temperature: person.temperature,\n renewalQuarter: person.renewalQuarter,\n jobTitle: data.profile?.jobTitle ?? null,\n customFields: data.customFields,\n tags: data.tags,\n }}\n onSaved={() => {\n // Invalidate dictionary caches so header badges pick up fresh colors\n void invalidateCustomerDictionary(queryClient, 'statuses')\n void invalidateCustomerDictionary(queryClient, 'lifecycle-stages')\n void invalidateCustomerDictionary(queryClient, 'sources')\n void invalidateCustomerDictionary(queryClient, 'temperature')\n void invalidateCustomerDictionary(queryClient, 'renewal-quarters')\n onDataReload?.()\n }}\n />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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 } 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.js";
|
|
14
|
-
import { getInitials } from "./utils.js";
|
|
15
15
|
function formatAssignedAt(value, t) {
|
|
16
16
|
if (!value) return null;
|
|
17
17
|
const date = new Date(value);
|
|
@@ -99,9 +99,8 @@ function RoleAssignmentRow({
|
|
|
99
99
|
}] : [],
|
|
100
100
|
[role.userEmail, role.userId, role.userName]
|
|
101
101
|
);
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
return getInitials(name || "?");
|
|
102
|
+
const userLabel = React.useMemo(() => {
|
|
103
|
+
return role.userName ?? role.userEmail ?? "?";
|
|
105
104
|
}, [role.userEmail, role.userName]);
|
|
106
105
|
const displayName = role.userName ?? role.userEmail ?? role.userId;
|
|
107
106
|
const assignedLabel = formatAssignedAt(role.createdAt, t);
|
|
@@ -124,7 +123,7 @@ function RoleAssignmentRow({
|
|
|
124
123
|
)
|
|
125
124
|
] }),
|
|
126
125
|
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex min-w-0 flex-col gap-3 sm:flex-row sm:items-start", children: [
|
|
127
|
-
/* @__PURE__ */ jsx(
|
|
126
|
+
/* @__PURE__ */ jsx(Avatar, { label: userLabel, size: "lg", variant: "monochrome" }),
|
|
128
127
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
129
128
|
/* @__PURE__ */ jsx("div", { className: "break-all text-sm font-semibold leading-5 text-foreground", children: displayName }),
|
|
130
129
|
role.userEmail ? /* @__PURE__ */ jsx("div", { className: "mt-1 break-all text-xs text-muted-foreground", children: role.userEmail }) : null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/RoleAssignmentRow.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Mail, Phone, X } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { LookupSelect, type LookupSelectItem } from '@open-mercato/ui/backend/inputs/LookupSelect'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport {
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Mail, Phone, X } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { LookupSelect, type LookupSelectItem } from '@open-mercato/ui/backend/inputs/LookupSelect'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { fetchAssignableStaffMembers } from './assignableStaff'\n\nexport interface RoleAssignment {\n id: string\n roleType: string\n userId: string\n userName?: string | null\n userEmail?: string | null\n userPhone?: string | null\n createdAt?: string\n updatedAt?: string\n}\n\ninterface RoleAssignmentRowProps {\n role: RoleAssignment\n roleTypeLabel: string\n runMutationWithContext: <T,>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n ) => Promise<T>\n entityType: 'company' | 'person'\n entityId: string\n onRemoved: () => void\n onUpdated: () => void\n}\n\nfunction formatAssignedAt(value: string | undefined, t: ReturnType<typeof useT>): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n const diffMs = Date.now() - date.getTime()\n const diffDays = Math.floor(diffMs / 86_400_000)\n if (diffDays <= 0) return t('customers.roles.assignedToday', 'Assigned today')\n if (diffDays === 1) return t('customers.roles.assignedYesterday', 'Assigned yesterday')\n return t('customers.roles.assignedDaysAgo', 'Assigned {{count}} days ago', { count: diffDays })\n}\n\nexport function RoleAssignmentRow({\n role,\n roleTypeLabel,\n runMutationWithContext,\n entityType,\n entityId,\n onRemoved,\n onUpdated,\n}: RoleAssignmentRowProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [removing, setRemoving] = React.useState(false)\n const [changingUser, setChangingUser] = React.useState(false)\n\n const searchUsers = React.useCallback(async (query: string): Promise<LookupSelectItem[]> => {\n try {\n const members = await fetchAssignableStaffMembers(query, { pageSize: 20 })\n return members.map((member) => ({\n id: member.userId,\n title: member.displayName,\n subtitle: member.displayName && member.email ? member.email : null,\n }))\n } catch (error) {\n console.error('customers.roles.searchUsers failed', error)\n return []\n }\n }, [])\n\n const handleUserChange = React.useCallback(async (userId: string | null) => {\n if (!userId || userId === role.userId) return\n const basePath = entityType === 'company' ? 'companies' : 'people'\n try {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/customers/${basePath}/${entityId}/roles?roleId=${role.id}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ userId }),\n }),\n { roleId: role.id, userId },\n )\n setChangingUser(false)\n onUpdated()\n } catch (error) {\n console.error('customers.roles.update failed', error)\n flash(t('customers.roles.updateFailed', 'Failed to update role assignment.'), 'error')\n setChangingUser(false)\n }\n }, [entityId, entityType, onUpdated, role.id, role.userId, runMutationWithContext, t])\n\n const handleRemove = React.useCallback(async () => {\n const confirmed = await confirm({\n title: t('customers.roles.removeConfirm', 'Remove this role assignment?'),\n variant: 'default',\n })\n if (!confirmed) return\n\n setRemoving(true)\n const basePath = entityType === 'company' ? 'companies' : 'people'\n try {\n await runMutationWithContext(\n () =>\n apiCallOrThrow(`/api/customers/${basePath}/${entityId}/roles?roleId=${role.id}`, {\n method: 'DELETE',\n }),\n { roleId: role.id },\n )\n onRemoved()\n } catch (error) {\n console.error('customers.roles.remove failed', error)\n flash(t('customers.roles.removeFailed', 'Failed to remove role assignment.'), 'error')\n } finally {\n setRemoving(false)\n }\n }, [confirm, entityId, entityType, onRemoved, role.id, runMutationWithContext, t])\n\n const currentUserOptions = React.useMemo<LookupSelectItem[]>(\n () =>\n role.userId\n ? [{\n id: role.userId,\n title: role.userName ?? role.userEmail ?? role.userId,\n subtitle: role.userName && role.userEmail ? role.userEmail : null,\n }]\n : [],\n [role.userEmail, role.userId, role.userName],\n )\n\n const userLabel = React.useMemo(() => {\n return role.userName ?? role.userEmail ?? '?'\n }, [role.userEmail, role.userName])\n\n const displayName = role.userName ?? role.userEmail ?? role.userId\n const assignedLabel = formatAssignedAt(role.createdAt, t)\n\n return (\n <>\n {ConfirmDialogElement}\n <div className=\"flex h-full min-w-0 flex-col overflow-hidden rounded-xl border bg-card p-4 shadow-sm\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <Badge variant=\"outline\" className=\"max-w-full break-words rounded-full px-2 py-0 text-left text-xs font-semibold\">\n {roleTypeLabel}\n </Badge>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleRemove}\n disabled={removing}\n aria-label={t('customers.roles.remove', 'Remove role')}\n >\n <X className=\"size-4\" />\n </IconButton>\n </div>\n\n <div className=\"mt-4 flex min-w-0 flex-col gap-3 sm:flex-row sm:items-start\">\n <Avatar label={userLabel} size=\"lg\" variant=\"monochrome\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"break-all text-sm font-semibold leading-5 text-foreground\">{displayName}</div>\n {role.userEmail ? (\n <div className=\"mt-1 break-all text-xs text-muted-foreground\">{role.userEmail}</div>\n ) : null}\n {assignedLabel ? (\n <div className=\"mt-2 text-overline uppercase tracking-[0.08em] text-muted-foreground\">\n {assignedLabel}\n </div>\n ) : null}\n </div>\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-2\">\n {role.userEmail ? (\n <IconButton asChild variant=\"outline\" size=\"sm\" className=\"shrink-0\">\n <a href={`mailto:${role.userEmail}`} aria-label={t('customers.roles.email', 'Send email')}>\n <Mail className=\"size-4\" />\n </a>\n </IconButton>\n ) : (\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"shrink-0\"\n disabled\n aria-label={t('customers.roles.emailUnavailable', 'Email unavailable')}\n >\n <Mail className=\"size-4\" />\n </IconButton>\n )}\n {role.userPhone ? (\n <IconButton asChild variant=\"outline\" size=\"sm\" className=\"shrink-0\">\n <a href={`tel:${role.userPhone}`} aria-label={t('customers.roles.call', 'Call')}>\n <Phone className=\"size-4\" />\n </a>\n </IconButton>\n ) : (\n <IconButton\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"shrink-0\"\n disabled\n aria-label={t('customers.roles.phoneUnavailable', 'Phone unavailable')}\n >\n <Phone className=\"size-4\" />\n </IconButton>\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto w-full justify-start px-2 py-1 text-xs sm:ml-auto sm:w-auto\"\n onClick={() => setChangingUser((current) => !current)}\n >\n {t('customers.roles.changeUser', 'Change user')}\n </Button>\n </div>\n\n {changingUser ? (\n <div className=\"mt-4 border-t pt-4\">\n <LookupSelect\n value={role.userId}\n onChange={async (userId) => {\n await handleUserChange(userId)\n }}\n fetchItems={searchUsers}\n options={currentUserOptions}\n placeholder={t('customers.roles.searchPlaceholder', 'Search team member...')}\n />\n </div>\n ) : null}\n </div>\n </>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -133,12 +133,6 @@ function createDictionarySelectLabels(kind, translate) {
|
|
|
133
133
|
return base;
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
-
function getInitials(name) {
|
|
137
|
-
const words = name.trim().split(/\s+/);
|
|
138
|
-
if (words.length === 0 || !words[0]) return "?";
|
|
139
|
-
if (words.length === 1) return words[0].charAt(0).toUpperCase();
|
|
140
|
-
return (words[0].charAt(0) + words[words.length - 1].charAt(0)).toUpperCase();
|
|
141
|
-
}
|
|
142
136
|
function formatCurrency(amount, currency) {
|
|
143
137
|
try {
|
|
144
138
|
return new Intl.NumberFormat(void 0, {
|
|
@@ -159,7 +153,6 @@ export {
|
|
|
159
153
|
formatDate,
|
|
160
154
|
formatFallbackLabel,
|
|
161
155
|
formatTemplate,
|
|
162
|
-
getInitials,
|
|
163
156
|
resolveTodoApiPath,
|
|
164
157
|
resolveTodoHref,
|
|
165
158
|
toLocalDateTimeInput
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/utils.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport type { DictionarySelectLabels } from '@open-mercato/core/modules/dictionaries/components/DictionaryEntrySelect'\nimport type { CustomerDictionaryKind } from '../../lib/dictionaries'\nimport { CUSTOMER_INTERACTION_TASK_SOURCE, CUSTOMER_INTERACTION_TASK_TYPE } from '../../lib/interactionCompatibility'\n\n\nexport function formatDate(value?: string | null): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString()\n}\n\nexport function formatTemplate(template: string, params?: Record<string, string | number>): string {\n if (!template) return template\n if (!params) return template\n return template.replace(/\\{\\{(\\w+)\\}\\}|\\{(\\w+)\\}/g, (match, doubleKey, singleKey) => {\n const key = doubleKey ?? singleKey\n if (!key) return match\n const value = params[key]\n return value === undefined ? match : String(value)\n })\n}\n\nexport function toLocalDateTimeInput(value?: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n const pad = (input: number) => `${input}`.padStart(2, '0')\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(\n date.getMinutes(),\n )}`\n}\n\nexport function resolveTodoHref(source: string, todoId: string | null | undefined): string | null {\n if (!todoId) return null\n if (!source) return null\n if (source === CUSTOMER_INTERACTION_TASK_SOURCE || source === CUSTOMER_INTERACTION_TASK_TYPE) return null\n const [module] = source.split(':')\n if (!module) return null\n if (module === 'example') {\n return `/backend/todos/${encodeURIComponent(todoId)}/edit`\n }\n return `/backend/${module}/todos/${encodeURIComponent(todoId)}/edit`\n}\n\nexport function resolveTodoApiPath(source: string): string | null {\n if (!source) return null\n if (source === CUSTOMER_INTERACTION_TASK_SOURCE || source === CUSTOMER_INTERACTION_TASK_TYPE) {\n return '/api/customers/todos'\n }\n const [module] = source.split(':')\n if (!module) return null\n return `/api/${module}/todos`\n}\n\nexport function createDictionarySelectLabels(\n kind: CustomerDictionaryKind,\n translate: (key: string, fallback: string) => string,\n): DictionarySelectLabels {\n const base = {\n valueLabel: translate('customers.people.form.dictionary.valueLabel', 'Name'),\n valuePlaceholder: translate('customers.people.form.dictionary.valuePlaceholder', 'Name'),\n labelLabel: translate('customers.people.form.dictionary.labelLabel', 'Label'),\n labelPlaceholder: translate('customers.people.form.dictionary.labelPlaceholder', 'Display name shown in UI'),\n emptyError: translate('customers.people.form.dictionary.errorRequired', 'Please enter a name'),\n cancelLabel: translate('customers.people.form.dictionary.cancel', 'Cancel'),\n saveLabel: translate('customers.people.form.dictionary.save', 'Save'),\n saveShortcutHint: translate('customers.people.form.dictionary.saveShortcut', '\u2318/Ctrl + Enter'),\n errorLoad: translate('customers.people.form.dictionary.errorLoad', 'Failed to load options'),\n errorSave: translate('customers.people.form.dictionary.error', 'Failed to save option'),\n loadingLabel: translate('customers.people.form.dictionary.loading', 'Loading\u2026'),\n manageTitle: translate('customers.people.form.dictionary.manage', 'Manage dictionary'),\n placeholder: translate('customers.people.form.dictionary.placeholder', 'Select an option'),\n addLabel: translate('customers.people.form.dictionary.add', 'Add option'),\n addPrompt: translate('customers.people.form.dictionary.prompt', 'Name your option'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitle', 'Add option'),\n } satisfies DictionarySelectLabels\n\n switch (kind) {\n case 'statuses':\n return {\n ...base,\n placeholder: translate('customers.people.form.status.placeholder', 'Select a status'),\n addLabel: translate('customers.people.form.dictionary.addStatus', 'Add status'),\n addPrompt: translate('customers.people.form.dictionary.promptStatus', 'Name the status'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleStatus', 'Add status'),\n }\n case 'lifecycle-stages':\n return {\n ...base,\n placeholder: translate('customers.people.form.lifecycleStage.placeholder', 'Select a lifecycle stage'),\n addLabel: translate('customers.people.form.dictionary.addLifecycleStage', 'Add lifecycle stage'),\n addPrompt: translate('customers.people.form.dictionary.promptLifecycleStage', 'Name the lifecycle stage'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleLifecycleStage', 'Add lifecycle stage'),\n }\n case 'sources':\n return {\n ...base,\n placeholder: translate('customers.people.form.source.placeholder', 'Select a source'),\n addLabel: translate('customers.people.form.dictionary.addSource', 'Add source'),\n addPrompt: translate('customers.people.form.dictionary.promptSource', 'Name the source'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleSource', 'Add source'),\n }\n case 'activity-types':\n return {\n ...base,\n placeholder: translate('customers.people.form.activityType.placeholder', 'Select an activity type'),\n addLabel: translate('customers.people.form.dictionary.addActivityType', 'Add activity type'),\n addPrompt: translate('customers.people.form.dictionary.promptActivityType', 'Name the activity type'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleActivityType', 'Add activity type'),\n }\n case 'deal-statuses':\n return {\n ...base,\n placeholder: translate('customers.deals.form.status.placeholder', 'Select a deal status'),\n addLabel: translate('customers.deals.form.dictionary.addStatus', 'Add deal status'),\n addPrompt: translate('customers.deals.form.dictionary.promptStatus', 'Name the deal status'),\n dialogTitle: translate('customers.deals.form.dictionary.dialogTitleStatus', 'Add deal status'),\n }\n case 'pipeline-stages':\n return {\n ...base,\n placeholder: translate('customers.deals.form.pipeline.placeholder', 'Select a pipeline stage'),\n addLabel: translate('customers.deals.form.dictionary.addPipelineStage', 'Add pipeline stage'),\n addPrompt: translate('customers.deals.form.dictionary.promptPipelineStage', 'Name the pipeline stage'),\n dialogTitle: translate('customers.deals.form.dictionary.dialogTitlePipelineStage', 'Add pipeline stage'),\n }\n case 'job-titles':\n return {\n ...base,\n placeholder: translate('customers.people.form.jobTitle.placeholder', 'Select a job title'),\n addLabel: translate('customers.people.form.dictionary.addJobTitle', 'Add job title'),\n addPrompt: translate('customers.people.form.dictionary.promptJobTitle', 'Name the job title'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleJobTitle', 'Add job title'),\n }\n case 'address-types':\n return {\n ...base,\n placeholder: translate('customers.people.form.addressType.placeholder', 'Select an address type'),\n addLabel: translate('customers.people.form.dictionary.addAddressType', 'Add address type'),\n addPrompt: translate('customers.people.form.dictionary.promptAddressType', 'Name the address type'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleAddressType', 'Add address type'),\n }\n default:\n return base\n }\n}\n\nexport function
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport type { DictionarySelectLabels } from '@open-mercato/core/modules/dictionaries/components/DictionaryEntrySelect'\nimport type { CustomerDictionaryKind } from '../../lib/dictionaries'\nimport { CUSTOMER_INTERACTION_TASK_SOURCE, CUSTOMER_INTERACTION_TASK_TYPE } from '../../lib/interactionCompatibility'\n\n\nexport function formatDate(value?: string | null): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString()\n}\n\nexport function formatTemplate(template: string, params?: Record<string, string | number>): string {\n if (!template) return template\n if (!params) return template\n return template.replace(/\\{\\{(\\w+)\\}\\}|\\{(\\w+)\\}/g, (match, doubleKey, singleKey) => {\n const key = doubleKey ?? singleKey\n if (!key) return match\n const value = params[key]\n return value === undefined ? match : String(value)\n })\n}\n\nexport function toLocalDateTimeInput(value?: string | null): string {\n if (!value) return ''\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return ''\n const pad = (input: number) => `${input}`.padStart(2, '0')\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(\n date.getMinutes(),\n )}`\n}\n\nexport function resolveTodoHref(source: string, todoId: string | null | undefined): string | null {\n if (!todoId) return null\n if (!source) return null\n if (source === CUSTOMER_INTERACTION_TASK_SOURCE || source === CUSTOMER_INTERACTION_TASK_TYPE) return null\n const [module] = source.split(':')\n if (!module) return null\n if (module === 'example') {\n return `/backend/todos/${encodeURIComponent(todoId)}/edit`\n }\n return `/backend/${module}/todos/${encodeURIComponent(todoId)}/edit`\n}\n\nexport function resolveTodoApiPath(source: string): string | null {\n if (!source) return null\n if (source === CUSTOMER_INTERACTION_TASK_SOURCE || source === CUSTOMER_INTERACTION_TASK_TYPE) {\n return '/api/customers/todos'\n }\n const [module] = source.split(':')\n if (!module) return null\n return `/api/${module}/todos`\n}\n\nexport function createDictionarySelectLabels(\n kind: CustomerDictionaryKind,\n translate: (key: string, fallback: string) => string,\n): DictionarySelectLabels {\n const base = {\n valueLabel: translate('customers.people.form.dictionary.valueLabel', 'Name'),\n valuePlaceholder: translate('customers.people.form.dictionary.valuePlaceholder', 'Name'),\n labelLabel: translate('customers.people.form.dictionary.labelLabel', 'Label'),\n labelPlaceholder: translate('customers.people.form.dictionary.labelPlaceholder', 'Display name shown in UI'),\n emptyError: translate('customers.people.form.dictionary.errorRequired', 'Please enter a name'),\n cancelLabel: translate('customers.people.form.dictionary.cancel', 'Cancel'),\n saveLabel: translate('customers.people.form.dictionary.save', 'Save'),\n saveShortcutHint: translate('customers.people.form.dictionary.saveShortcut', '\u2318/Ctrl + Enter'),\n errorLoad: translate('customers.people.form.dictionary.errorLoad', 'Failed to load options'),\n errorSave: translate('customers.people.form.dictionary.error', 'Failed to save option'),\n loadingLabel: translate('customers.people.form.dictionary.loading', 'Loading\u2026'),\n manageTitle: translate('customers.people.form.dictionary.manage', 'Manage dictionary'),\n placeholder: translate('customers.people.form.dictionary.placeholder', 'Select an option'),\n addLabel: translate('customers.people.form.dictionary.add', 'Add option'),\n addPrompt: translate('customers.people.form.dictionary.prompt', 'Name your option'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitle', 'Add option'),\n } satisfies DictionarySelectLabels\n\n switch (kind) {\n case 'statuses':\n return {\n ...base,\n placeholder: translate('customers.people.form.status.placeholder', 'Select a status'),\n addLabel: translate('customers.people.form.dictionary.addStatus', 'Add status'),\n addPrompt: translate('customers.people.form.dictionary.promptStatus', 'Name the status'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleStatus', 'Add status'),\n }\n case 'lifecycle-stages':\n return {\n ...base,\n placeholder: translate('customers.people.form.lifecycleStage.placeholder', 'Select a lifecycle stage'),\n addLabel: translate('customers.people.form.dictionary.addLifecycleStage', 'Add lifecycle stage'),\n addPrompt: translate('customers.people.form.dictionary.promptLifecycleStage', 'Name the lifecycle stage'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleLifecycleStage', 'Add lifecycle stage'),\n }\n case 'sources':\n return {\n ...base,\n placeholder: translate('customers.people.form.source.placeholder', 'Select a source'),\n addLabel: translate('customers.people.form.dictionary.addSource', 'Add source'),\n addPrompt: translate('customers.people.form.dictionary.promptSource', 'Name the source'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleSource', 'Add source'),\n }\n case 'activity-types':\n return {\n ...base,\n placeholder: translate('customers.people.form.activityType.placeholder', 'Select an activity type'),\n addLabel: translate('customers.people.form.dictionary.addActivityType', 'Add activity type'),\n addPrompt: translate('customers.people.form.dictionary.promptActivityType', 'Name the activity type'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleActivityType', 'Add activity type'),\n }\n case 'deal-statuses':\n return {\n ...base,\n placeholder: translate('customers.deals.form.status.placeholder', 'Select a deal status'),\n addLabel: translate('customers.deals.form.dictionary.addStatus', 'Add deal status'),\n addPrompt: translate('customers.deals.form.dictionary.promptStatus', 'Name the deal status'),\n dialogTitle: translate('customers.deals.form.dictionary.dialogTitleStatus', 'Add deal status'),\n }\n case 'pipeline-stages':\n return {\n ...base,\n placeholder: translate('customers.deals.form.pipeline.placeholder', 'Select a pipeline stage'),\n addLabel: translate('customers.deals.form.dictionary.addPipelineStage', 'Add pipeline stage'),\n addPrompt: translate('customers.deals.form.dictionary.promptPipelineStage', 'Name the pipeline stage'),\n dialogTitle: translate('customers.deals.form.dictionary.dialogTitlePipelineStage', 'Add pipeline stage'),\n }\n case 'job-titles':\n return {\n ...base,\n placeholder: translate('customers.people.form.jobTitle.placeholder', 'Select a job title'),\n addLabel: translate('customers.people.form.dictionary.addJobTitle', 'Add job title'),\n addPrompt: translate('customers.people.form.dictionary.promptJobTitle', 'Name the job title'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleJobTitle', 'Add job title'),\n }\n case 'address-types':\n return {\n ...base,\n placeholder: translate('customers.people.form.addressType.placeholder', 'Select an address type'),\n addLabel: translate('customers.people.form.dictionary.addAddressType', 'Add address type'),\n addPrompt: translate('customers.people.form.dictionary.promptAddressType', 'Name the address type'),\n dialogTitle: translate('customers.people.form.dictionary.dialogTitleAddressType', 'Add address type'),\n }\n default:\n return base\n }\n}\n\nexport function formatCurrency(amount: number, currency?: string | null): string {\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: currency || 'PLN',\n maximumFractionDigits: 0,\n }).format(amount)\n } catch {\n return `${amount.toLocaleString()} ${currency || 'PLN'}`\n }\n}\n\nexport function formatFallbackLabel(value: string): string {\n return value\n .replace(/[_-]+/g, ' ')\n .replace(/^\\w/, (c) => c.toUpperCase())\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -8,6 +8,7 @@ import { useAiDock } from "@open-mercato/ui/ai/AiDock";
|
|
|
8
8
|
import { useAiChatSessions } from "@open-mercato/ui/ai/AiChatSessions";
|
|
9
9
|
import { ChatPaneTabs } from "@open-mercato/ui/ai/ChatPaneTabs";
|
|
10
10
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
11
|
+
import { ButtonGroup } from "@open-mercato/ui/primitives/button-group";
|
|
11
12
|
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
12
13
|
import {
|
|
13
14
|
Dialog,
|
|
@@ -436,7 +437,7 @@ function AiAssistantTriggerWidget({ context }) {
|
|
|
436
437
|
"Choose an AI assistant"
|
|
437
438
|
);
|
|
438
439
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
439
|
-
/* @__PURE__ */ jsxs(
|
|
440
|
+
/* @__PURE__ */ jsxs(ButtonGroup, { children: [
|
|
440
441
|
/* @__PURE__ */ jsxs(
|
|
441
442
|
Button,
|
|
442
443
|
{
|
|
@@ -446,11 +447,7 @@ function AiAssistantTriggerWidget({ context }) {
|
|
|
446
447
|
"data-ai-customers-inject-trigger": "",
|
|
447
448
|
"aria-label": triggerLabel,
|
|
448
449
|
title: triggerLabel,
|
|
449
|
-
className: cn(
|
|
450
|
-
"relative",
|
|
451
|
-
"hover:bg-brand-violet/10",
|
|
452
|
-
agents.length > 1 && "rounded-r-none border-r-0"
|
|
453
|
-
),
|
|
450
|
+
className: cn("relative", "hover:bg-brand-violet/10"),
|
|
454
451
|
children: [
|
|
455
452
|
/* @__PURE__ */ jsx(AiIcon, { className: "size-4" }),
|
|
456
453
|
/* @__PURE__ */ jsx("span", { children: labelText }),
|
|
@@ -471,10 +468,8 @@ function AiAssistantTriggerWidget({ context }) {
|
|
|
471
468
|
{
|
|
472
469
|
type: "button",
|
|
473
470
|
variant: "outline",
|
|
474
|
-
size: "lg",
|
|
475
471
|
"aria-label": moreAgentsLabel,
|
|
476
472
|
title: moreAgentsLabel,
|
|
477
|
-
className: "rounded-l-none",
|
|
478
473
|
"data-ai-customers-inject-picker": "",
|
|
479
474
|
children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4", "aria-hidden": true })
|
|
480
475
|
}
|