@open-mercato/core 0.6.5-develop.5382.1.f542de69af → 0.6.6-develop.5412.1.e2a52b14f0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/crmFixtures.js +4 -0
  3. package/dist/helpers/integration/crmFixtures.js.map +2 -2
  4. package/dist/modules/attachments/api/route.js +2 -0
  5. package/dist/modules/attachments/api/route.js.map +2 -2
  6. package/dist/modules/attachments/lib/access.js +18 -0
  7. package/dist/modules/attachments/lib/access.js.map +2 -2
  8. package/dist/modules/auth/services/rbacService.js +3 -2
  9. package/dist/modules/auth/services/rbacService.js.map +2 -2
  10. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +0 -3
  11. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +2 -2
  12. package/dist/modules/customers/api/deals/route.js +43 -2
  13. package/dist/modules/customers/api/deals/route.js.map +2 -2
  14. package/dist/modules/customers/api/deals/summary/route.js +402 -0
  15. package/dist/modules/customers/api/deals/summary/route.js.map +7 -0
  16. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js +16 -5
  17. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js.map +2 -2
  18. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js +22 -5
  19. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js.map +2 -2
  20. package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -2
  21. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  22. package/dist/modules/customers/backend/customers/deals/page.js +221 -56
  23. package/dist/modules/customers/backend/customers/deals/page.js.map +3 -3
  24. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
  25. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  26. package/dist/modules/customers/cli.js +15 -9
  27. package/dist/modules/customers/cli.js.map +2 -2
  28. package/dist/modules/customers/components/DealsKpiStrip.js +282 -0
  29. package/dist/modules/customers/components/DealsKpiStrip.js.map +7 -0
  30. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +0 -1
  31. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
  32. package/dist/modules/customers/components/detail/DealForm.js +100 -17
  33. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  34. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +1 -2
  35. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  36. package/dist/modules/customers/components/kpi/PipelineStageBar.js +63 -0
  37. package/dist/modules/customers/components/kpi/PipelineStageBar.js.map +7 -0
  38. package/dist/modules/customers/lib/dealsMetrics.js +82 -0
  39. package/dist/modules/customers/lib/dealsMetrics.js.map +7 -0
  40. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +2 -1
  41. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +2 -2
  42. package/dist/modules/directory/utils/organizationScope.js +59 -27
  43. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  44. package/dist/modules/entities/api/entities.js +7 -0
  45. package/dist/modules/entities/api/entities.js.map +2 -2
  46. package/dist/modules/entities/api/records.js +26 -15
  47. package/dist/modules/entities/api/records.js.map +2 -2
  48. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +14 -0
  49. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
  50. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js +14 -0
  51. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js.map +2 -2
  52. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +12 -0
  53. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  54. package/dist/modules/entities/components/useRecordsEntityGuard.js +30 -0
  55. package/dist/modules/entities/components/useRecordsEntityGuard.js.map +7 -0
  56. package/dist/modules/query_index/lib/engine.js +4 -2
  57. package/dist/modules/query_index/lib/engine.js.map +2 -2
  58. package/dist/modules/staff/api/team-members.js +9 -2
  59. package/dist/modules/staff/api/team-members.js.map +2 -2
  60. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +24 -1
  61. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
  62. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +11 -6
  63. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  64. package/dist/modules/staff/commands/team-members.js +1 -1
  65. package/dist/modules/staff/commands/team-members.js.map +2 -2
  66. package/dist/modules/staff/components/TeamMemberForm.js +1 -1
  67. package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
  68. package/dist/modules/staff/lib/scheduleSwitch.js +23 -0
  69. package/dist/modules/staff/lib/scheduleSwitch.js.map +7 -0
  70. package/dist/modules/workflows/backend/definitions/create/page.js +1 -2
  71. package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
  72. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +1 -2
  73. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  74. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -2
  75. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  76. package/dist/modules/workflows/components/NodeEditDialog.js +4 -13
  77. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  78. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js +4 -13
  79. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +2 -2
  80. package/dist/modules/workflows/components/WorkflowGraphImpl.js +1 -4
  81. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  82. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +2 -5
  83. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +2 -2
  84. package/package.json +8 -8
  85. package/src/helpers/integration/crmFixtures.ts +21 -1
  86. package/src/modules/attachments/AGENTS.md +79 -0
  87. package/src/modules/attachments/api/route.ts +2 -0
  88. package/src/modules/attachments/lib/access.ts +36 -0
  89. package/src/modules/auth/services/rbacService.ts +11 -2
  90. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +0 -3
  91. package/src/modules/customers/api/deals/route.ts +51 -2
  92. package/src/modules/customers/api/deals/summary/route.ts +496 -0
  93. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.ts +28 -6
  94. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealData.ts +33 -6
  95. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +17 -2
  96. package/src/modules/customers/backend/customers/deals/page.tsx +254 -66
  97. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +1 -2
  98. package/src/modules/customers/cli.ts +15 -15
  99. package/src/modules/customers/components/DealsKpiStrip.tsx +389 -0
  100. package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +0 -1
  101. package/src/modules/customers/components/detail/DealForm.tsx +121 -19
  102. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +1 -2
  103. package/src/modules/customers/components/kpi/PipelineStageBar.tsx +77 -0
  104. package/src/modules/customers/i18n/de.json +43 -0
  105. package/src/modules/customers/i18n/en.json +43 -0
  106. package/src/modules/customers/i18n/es.json +43 -0
  107. package/src/modules/customers/i18n/pl.json +43 -0
  108. package/src/modules/customers/lib/dealsMetrics.ts +159 -0
  109. package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +3 -1
  110. package/src/modules/directory/utils/organizationScope.ts +85 -30
  111. package/src/modules/entities/api/entities.ts +11 -0
  112. package/src/modules/entities/api/records.ts +46 -25
  113. package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +15 -0
  114. package/src/modules/entities/backend/entities/user/[entityId]/records/create/page.tsx +15 -0
  115. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +23 -0
  116. package/src/modules/entities/components/useRecordsEntityGuard.ts +41 -0
  117. package/src/modules/entities/i18n/de.json +1 -0
  118. package/src/modules/entities/i18n/en.json +1 -0
  119. package/src/modules/entities/i18n/es.json +1 -0
  120. package/src/modules/entities/i18n/pl.json +1 -0
  121. package/src/modules/query_index/lib/engine.ts +11 -5
  122. package/src/modules/staff/api/team-members.ts +9 -2
  123. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +31 -1
  124. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +18 -8
  125. package/src/modules/staff/commands/team-members.ts +5 -2
  126. package/src/modules/staff/components/TeamMemberForm.tsx +4 -1
  127. package/src/modules/staff/i18n/de.json +1 -0
  128. package/src/modules/staff/i18n/en.json +1 -0
  129. package/src/modules/staff/i18n/es.json +1 -0
  130. package/src/modules/staff/i18n/pl.json +1 -0
  131. package/src/modules/staff/lib/scheduleSwitch.ts +46 -0
  132. package/src/modules/workflows/backend/definitions/create/page.tsx +1 -2
  133. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +1 -2
  134. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -2
  135. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -4
  136. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +4 -7
  137. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +1 -2
  138. package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +2 -3
@@ -38,6 +38,9 @@ export type DealFormBaseValues = {
38
38
  companyIds?: string[]
39
39
  }
40
40
 
41
+ type PipelineOption = { id: string; name: string; isDefault: boolean }
42
+ type PipelineStageOption = { id: string; label: string; order: number }
43
+
41
44
  export type DealFormSubmitPayload = {
42
45
  base: DealFormBaseValues
43
46
  custom: Record<string, unknown>
@@ -64,6 +67,8 @@ export type DealFormProps = {
64
67
  showAssociationsGroup?: boolean
65
68
  showVersionHistory?: boolean
66
69
  showCancelAction?: boolean
70
+ initialPipelineOptions?: PipelineOption[]
71
+ initialPipelineStageOptions?: PipelineStageOption[]
67
72
  }
68
73
 
69
74
  type EntityOption = {
@@ -92,6 +97,84 @@ type EntityMultiSelectProps = {
92
97
 
93
98
  const DEAL_ENTITY_IDS = [E.customers.customer_deal]
94
99
  const CURRENCY_PRIORITY = ['EUR', 'USD', 'GBP', 'PLN'] as const
100
+ const PIPELINE_OPTIONS_TTL_MS = 60_000
101
+ const PIPELINE_STAGE_OPTIONS_TTL_MS = 30_000
102
+
103
+ type MetadataCacheEntry<T> = {
104
+ expiresAt: number
105
+ promise: Promise<T>
106
+ }
107
+
108
+ let pipelineOptionsCache: MetadataCacheEntry<PipelineOption[]> | null = null
109
+ const pipelineStageOptionsCache = new Map<string, MetadataCacheEntry<PipelineStageOption[]>>()
110
+
111
+ function isFreshCacheEntry<T>(entry: MetadataCacheEntry<T> | null | undefined): entry is MetadataCacheEntry<T> {
112
+ return Boolean(entry && entry.expiresAt > Date.now())
113
+ }
114
+
115
+ function normalizePipelineOptions(options: PipelineOption[] | undefined): PipelineOption[] {
116
+ const byId = new Map<string, PipelineOption>()
117
+ for (const option of options ?? []) {
118
+ if (!option.id) continue
119
+ byId.set(option.id, {
120
+ id: option.id,
121
+ name: option.name,
122
+ isDefault: option.isDefault === true,
123
+ })
124
+ }
125
+ return Array.from(byId.values())
126
+ }
127
+
128
+ function mergePipelineOptions(seed: PipelineOption[], loaded: PipelineOption[]): PipelineOption[] {
129
+ const byId = new Map<string, PipelineOption>()
130
+ for (const option of seed) byId.set(option.id, option)
131
+ for (const option of loaded) byId.set(option.id, option)
132
+ return Array.from(byId.values())
133
+ }
134
+
135
+ function normalizePipelineStageOptions(options: PipelineStageOption[] | undefined): PipelineStageOption[] {
136
+ return [...(options ?? [])]
137
+ .filter((option) => option.id)
138
+ .sort((left, right) => left.order - right.order)
139
+ }
140
+
141
+ async function fetchPipelineOptions(): Promise<PipelineOption[]> {
142
+ if (isFreshCacheEntry(pipelineOptionsCache)) return pipelineOptionsCache.promise
143
+ const entry: MetadataCacheEntry<PipelineOption[]> = {
144
+ expiresAt: Date.now() + PIPELINE_OPTIONS_TTL_MS,
145
+ promise: apiCall<{ items: PipelineOption[] }>('/api/customers/pipelines')
146
+ .then((call) => (call.ok && call.result?.items ? normalizePipelineOptions(call.result.items) : [])),
147
+ }
148
+ pipelineOptionsCache = entry
149
+ try {
150
+ return await entry.promise
151
+ } catch (error) {
152
+ if (pipelineOptionsCache === entry) pipelineOptionsCache = null
153
+ throw error
154
+ }
155
+ }
156
+
157
+ async function fetchPipelineStageOptions(pipelineId: string): Promise<PipelineStageOption[]> {
158
+ const cached = pipelineStageOptionsCache.get(pipelineId)
159
+ if (isFreshCacheEntry(cached)) return cached.promise
160
+ const entry: MetadataCacheEntry<PipelineStageOption[]> = {
161
+ expiresAt: Date.now() + PIPELINE_STAGE_OPTIONS_TTL_MS,
162
+ promise: apiCall<{ items: PipelineStageOption[] }>(`/api/customers/pipeline-stages?pipelineId=${encodeURIComponent(pipelineId)}`)
163
+ .then((call) => (call.ok && call.result?.items ? normalizePipelineStageOptions(call.result.items) : [])),
164
+ }
165
+ pipelineStageOptionsCache.set(pipelineId, entry)
166
+ try {
167
+ return await entry.promise
168
+ } catch (error) {
169
+ if (pipelineStageOptionsCache.get(pipelineId) === entry) pipelineStageOptionsCache.delete(pipelineId)
170
+ throw error
171
+ }
172
+ }
173
+
174
+ export function resetDealPipelineMetadataCacheForTests() {
175
+ pipelineOptionsCache = null
176
+ pipelineStageOptionsCache.clear()
177
+ }
95
178
 
96
179
  const schema = z.object({
97
180
  title: z
@@ -647,6 +730,8 @@ export function DealForm({
647
730
  showAssociationsGroup = true,
648
731
  showVersionHistory = true,
649
732
  showCancelAction = true,
733
+ initialPipelineOptions,
734
+ initialPipelineStageOptions,
650
735
  }: DealFormProps) {
651
736
  const t = useT()
652
737
  const [pending, setPending] = React.useState(false)
@@ -733,25 +818,36 @@ export function DealForm({
733
818
  const disabled = pending || isSubmitting
734
819
  const canDelete = mode === 'edit' && typeof onDelete === 'function'
735
820
 
736
- type PipelineOption = { id: string; name: string; isDefault: boolean }
737
- type PipelineStageOption = { id: string; label: string; order: number }
821
+ const mountedRef = React.useRef(false)
822
+ const seedPipelineOptions = React.useMemo(
823
+ () => normalizePipelineOptions(initialPipelineOptions),
824
+ [initialPipelineOptions],
825
+ )
826
+ const seedPipelineStageOptions = React.useMemo(
827
+ () => Array.isArray(initialPipelineStageOptions) ? normalizePipelineStageOptions(initialPipelineStageOptions) : null,
828
+ [initialPipelineStageOptions],
829
+ )
830
+
831
+ const [pipelines, setPipelines] = React.useState<PipelineOption[]>(() => seedPipelineOptions)
832
+ const [pipelineStages, setPipelineStages] = React.useState<PipelineStageOption[]>(() => seedPipelineStageOptions ?? [])
738
833
 
739
- const [pipelines, setPipelines] = React.useState<PipelineOption[]>([])
740
- const [pipelineStages, setPipelineStages] = React.useState<PipelineStageOption[]>([])
834
+ React.useEffect(() => {
835
+ mountedRef.current = true
836
+ return () => {
837
+ mountedRef.current = false
838
+ }
839
+ }, [])
741
840
 
742
841
  const loadStagesForPipeline = React.useCallback(async (pipelineId: string) => {
743
842
  if (!pipelineId) {
744
- setPipelineStages([])
843
+ if (mountedRef.current) setPipelineStages([])
745
844
  return
746
845
  }
747
846
  try {
748
- const call = await apiCall<{ items: PipelineStageOption[] }>(`/api/customers/pipeline-stages?pipelineId=${encodeURIComponent(pipelineId)}`)
749
- if (call.ok && call.result?.items) {
750
- const sorted = [...call.result.items].sort((a, b) => a.order - b.order)
751
- setPipelineStages(sorted)
752
- }
847
+ const stages = await fetchPipelineStageOptions(pipelineId)
848
+ if (mountedRef.current) setPipelineStages(stages)
753
849
  } catch {
754
- setPipelineStages([])
850
+ if (mountedRef.current) setPipelineStages([])
755
851
  }
756
852
  }, [])
757
853
 
@@ -759,24 +855,30 @@ export function DealForm({
759
855
  let cancelled = false
760
856
  ;(async () => {
761
857
  try {
762
- const call = await apiCall<{ items: PipelineOption[] }>('/api/customers/pipelines')
763
- if (cancelled) return
764
- if (call.ok && call.result?.items) {
765
- setPipelines(call.result.items)
766
- }
858
+ const loaded = await fetchPipelineOptions()
859
+ if (cancelled || !mountedRef.current) return
860
+ setPipelines(mergePipelineOptions(seedPipelineOptions, loaded))
767
861
  } catch {
768
- // ignore
862
+ if (!cancelled && mountedRef.current && seedPipelineOptions.length > 0) {
863
+ setPipelines(seedPipelineOptions)
864
+ }
769
865
  }
770
866
  })().catch(() => {})
771
867
  return () => { cancelled = true }
772
- }, [])
868
+ }, [seedPipelineOptions])
773
869
 
774
870
  React.useEffect(() => {
775
871
  const pid = initialValues?.pipelineId
776
872
  if (typeof pid === 'string' && pid.length) {
873
+ if (seedPipelineStageOptions) {
874
+ setPipelineStages(seedPipelineStageOptions)
875
+ return
876
+ }
777
877
  loadStagesForPipeline(pid).catch(() => {})
878
+ } else {
879
+ setPipelineStages([])
778
880
  }
779
- }, [initialValues?.pipelineId, loadStagesForPipeline])
881
+ }, [initialValues?.pipelineId, loadStagesForPipeline, seedPipelineStageOptions])
780
882
 
781
883
  const baseFields = React.useMemo<CrudField[]>(() => [
782
884
  {
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import * as React from 'react'
4
- import { Users, Phone, Check, Mail, Calendar, AlertTriangle, X, StickyNote } from 'lucide-react'
4
+ import { Users, Phone, Check, Mail, Calendar, X, StickyNote } from 'lucide-react'
5
5
  import { cn } from '@open-mercato/shared/lib/utils'
6
6
  import { useT } from '@open-mercato/shared/lib/i18n/context'
7
7
  import { validatePhoneNumber } from '@open-mercato/shared/lib/phone'
@@ -498,7 +498,6 @@ export function ScheduleActivityDialog({
498
498
  {/* Conflict warning */}
499
499
  {state.conflict && (
500
500
  <Alert variant="warning" className="rounded-lg">
501
- <AlertTriangle className="size-5" />
502
501
  <AlertTitle>
503
502
  {t('customers.schedule.conflict.title', 'Calendar conflict')}
504
503
  </AlertTitle>
@@ -0,0 +1,77 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { cn } from '@open-mercato/shared/lib/utils'
5
+ import type { DictionaryMap } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'
6
+
7
+ export type PipelineStageDatum = {
8
+ stage: string | null
9
+ count: number
10
+ value: number
11
+ }
12
+
13
+ export type PipelineStageBarProps = {
14
+ stages: PipelineStageDatum[]
15
+ stageDictionary: DictionaryMap
16
+ unassignedLabel: string
17
+ }
18
+
19
+ function resolveStageColor(stage: string | null, stageDictionary: DictionaryMap): string | null {
20
+ if (!stage) return null
21
+ const entry = stageDictionary[stage]
22
+ return entry?.color ?? null
23
+ }
24
+
25
+ function resolveStageLabel(stage: string | null, stageDictionary: DictionaryMap, unassignedLabel: string): string {
26
+ if (!stage) return unassignedLabel
27
+ return stageDictionary[stage]?.label ?? unassignedLabel
28
+ }
29
+
30
+ export function PipelineStageBar({ stages, stageDictionary, unassignedLabel }: PipelineStageBarProps) {
31
+ const segments = React.useMemo(
32
+ () => stages.filter((entry) => entry.count > 0),
33
+ [stages],
34
+ )
35
+
36
+ if (segments.length === 0) return null
37
+
38
+ return (
39
+ <div className="space-y-2">
40
+ <div className="flex h-2 w-full gap-1 overflow-hidden rounded-full">
41
+ {segments.map((entry, index) => {
42
+ const color = resolveStageColor(entry.stage, stageDictionary)
43
+ return (
44
+ <span
45
+ key={entry.stage ?? `unassigned-${index}`}
46
+ className={cn('h-full rounded-full', color ? null : 'bg-muted')}
47
+ style={{ flexGrow: entry.count, ...(color ? { backgroundColor: color } : {}) }}
48
+ aria-hidden
49
+ />
50
+ )
51
+ })}
52
+ </div>
53
+ <ul className="flex flex-wrap gap-x-3 gap-y-1">
54
+ {segments.map((entry, index) => {
55
+ const color = resolveStageColor(entry.stage, stageDictionary)
56
+ const label = resolveStageLabel(entry.stage, stageDictionary, unassignedLabel)
57
+ return (
58
+ <li
59
+ key={entry.stage ?? `unassigned-legend-${index}`}
60
+ className="inline-flex items-center gap-1.5 text-xs text-muted-foreground"
61
+ >
62
+ <span
63
+ className={cn('inline-block h-2 w-2 rounded-full border border-border', color ? null : 'bg-muted')}
64
+ style={color ? { backgroundColor: color } : undefined}
65
+ aria-hidden
66
+ />
67
+ <span>{label}</span>
68
+ <span className="font-medium text-foreground">{entry.count}</span>
69
+ </li>
70
+ )
71
+ })}
72
+ </ul>
73
+ </div>
74
+ )
75
+ }
76
+
77
+ export default PipelineStageBar
@@ -1154,6 +1154,9 @@
1154
1154
  "customers.deals.list.bulkDelete.progressName": "Ausgewählte Deals löschen",
1155
1155
  "customers.deals.list.bulkDelete.success": "{count} deals deleted",
1156
1156
  "customers.deals.list.bulkDelete.title": "Delete {count} deals?",
1157
+ "customers.deals.list.close.lost": "Verloren",
1158
+ "customers.deals.list.close.overdue": "Überfällig",
1159
+ "customers.deals.list.close.won": "Gewonnen",
1157
1160
  "customers.deals.list.columns.companies": "Unternehmen",
1158
1161
  "customers.deals.list.columns.expectedClose": "Voraussichtlicher Abschluss",
1159
1162
  "customers.deals.list.columns.owner": "Verantwortlich",
@@ -1171,12 +1174,52 @@
1171
1174
  "customers.deals.list.error.load": "Geschäftsliste konnte nicht geladen werden",
1172
1175
  "customers.deals.list.filters.companies": "Unternehmen",
1173
1176
  "customers.deals.list.filters.companiesPlaceholder": "Nach Unternehmen filtern",
1177
+ "customers.deals.list.filters.needsAttention": "Benötigt Aufmerksamkeit",
1178
+ "customers.deals.list.filters.needsAttentionRemove": "Filter \"Benötigt Aufmerksamkeit\" entfernen",
1174
1179
  "customers.deals.list.filters.people": "Personen",
1175
1180
  "customers.deals.list.filters.peoplePlaceholder": "Nach Personen filtern",
1181
+ "customers.deals.list.kpi.activeAcrossPipelines": "{deals} in {pipelines}",
1182
+ "customers.deals.list.kpi.activeDeals": "Aktive Deals",
1183
+ "customers.deals.list.kpi.avgDeal": "Ø Deal {value}",
1184
+ "customers.deals.list.kpi.currencyApproxMissing": "Näherungswert; Deals in {currencies} ohne Wechselkurs sind ausgeschlossen.",
1185
+ "customers.deals.list.kpi.currencyApproxNoBase": "Näherungswert; keine Basiswährung ist konfiguriert.",
1186
+ "customers.deals.list.kpi.deltaTooltip": "Im Vergleich zum Vorzeitraum",
1187
+ "customers.deals.list.kpi.deltaUnavailable": "Keine vergleichbare Änderung",
1188
+ "customers.deals.list.kpi.error": "Metriken konnten nicht geladen werden",
1189
+ "customers.deals.list.kpi.frag.activeDeals.few": "{count} aktive Deals",
1190
+ "customers.deals.list.kpi.frag.activeDeals.many": "{count} aktive Deals",
1191
+ "customers.deals.list.kpi.frag.activeDeals.one": "{count} aktiver Deal",
1192
+ "customers.deals.list.kpi.frag.activeDeals.other": "{count} aktive Deals",
1193
+ "customers.deals.list.kpi.frag.dealsClosed.few": "{count} Deals in diesem Quartal abgeschlossen",
1194
+ "customers.deals.list.kpi.frag.dealsClosed.many": "{count} Deals in diesem Quartal abgeschlossen",
1195
+ "customers.deals.list.kpi.frag.dealsClosed.one": "{count} Deal in diesem Quartal abgeschlossen",
1196
+ "customers.deals.list.kpi.frag.dealsClosed.other": "{count} Deals in diesem Quartal abgeschlossen",
1197
+ "customers.deals.list.kpi.frag.needAttention.few": "{count} benötigen Aufmerksamkeit",
1198
+ "customers.deals.list.kpi.frag.needAttention.many": "{count} benötigen Aufmerksamkeit",
1199
+ "customers.deals.list.kpi.frag.needAttention.one": "{count} benötigt Aufmerksamkeit",
1200
+ "customers.deals.list.kpi.frag.needAttention.other": "{count} benötigen Aufmerksamkeit",
1201
+ "customers.deals.list.kpi.frag.owners.few": "{count} Inhaber",
1202
+ "customers.deals.list.kpi.frag.owners.many": "{count} Inhaber",
1203
+ "customers.deals.list.kpi.frag.owners.one": "{count} Inhaber",
1204
+ "customers.deals.list.kpi.frag.owners.other": "{count} Inhaber",
1205
+ "customers.deals.list.kpi.frag.pipelines.few": "{count} Pipelines",
1206
+ "customers.deals.list.kpi.frag.pipelines.many": "{count} Pipelines",
1207
+ "customers.deals.list.kpi.frag.pipelines.one": "{count} Pipeline",
1208
+ "customers.deals.list.kpi.frag.pipelines.other": "{count} Pipelines",
1209
+ "customers.deals.list.kpi.fromLastQuarter": "von {value}% im letzten Quartal",
1210
+ "customers.deals.list.kpi.ownersNeedAttention": "{owners} · {attention}",
1211
+ "customers.deals.list.kpi.pipelineValue": "Pipeline-Wert",
1212
+ "customers.deals.list.kpi.retry": "Erneut versuchen",
1213
+ "customers.deals.list.kpi.scopeAllPipelinesThisQuarter": "Alle Pipelines · dieses Quartal",
1214
+ "customers.deals.list.kpi.unassignedStage": "Nicht zugewiesen",
1215
+ "customers.deals.list.kpi.updating": "Metriken werden aktualisiert",
1216
+ "customers.deals.list.kpi.winRate": "Gewinnrate",
1217
+ "customers.deals.list.kpi.wonThisQuarter": "Gewonnen dieses Quartal",
1176
1218
  "customers.deals.list.noValue": "Nicht gesetzt",
1177
1219
  "customers.deals.list.refresh": "Aktualisieren",
1178
1220
  "customers.deals.list.searchPlaceholder": "Geschäfte suchen…",
1179
1221
  "customers.deals.list.title": "Geschäfte",
1222
+ "customers.deals.list.unknownOwner": "Unbekannter Inhaber",
1180
1223
  "customers.deals.list.unnamedCompany": "Unbenanntes Unternehmen",
1181
1224
  "customers.deals.list.unnamedPerson": "Unbenannte Person",
1182
1225
  "customers.deals.pipeline.actions.openDeal": "Deal öffnen",
@@ -1154,6 +1154,9 @@
1154
1154
  "customers.deals.list.bulkDelete.progressName": "Delete selected deals",
1155
1155
  "customers.deals.list.bulkDelete.success": "{count} deals deleted",
1156
1156
  "customers.deals.list.bulkDelete.title": "Delete {count} deals?",
1157
+ "customers.deals.list.close.lost": "Lost",
1158
+ "customers.deals.list.close.overdue": "Overdue",
1159
+ "customers.deals.list.close.won": "Won",
1157
1160
  "customers.deals.list.columns.companies": "Companies",
1158
1161
  "customers.deals.list.columns.expectedClose": "Expected close",
1159
1162
  "customers.deals.list.columns.owner": "Owner",
@@ -1171,12 +1174,52 @@
1171
1174
  "customers.deals.list.error.load": "Unable to load deals list",
1172
1175
  "customers.deals.list.filters.companies": "Companies",
1173
1176
  "customers.deals.list.filters.companiesPlaceholder": "Filter by companies",
1177
+ "customers.deals.list.filters.needsAttention": "Needs attention",
1178
+ "customers.deals.list.filters.needsAttentionRemove": "Remove needs attention filter",
1174
1179
  "customers.deals.list.filters.people": "People",
1175
1180
  "customers.deals.list.filters.peoplePlaceholder": "Filter by people",
1181
+ "customers.deals.list.kpi.activeAcrossPipelines": "{deals} across {pipelines}",
1182
+ "customers.deals.list.kpi.activeDeals": "Active deals",
1183
+ "customers.deals.list.kpi.avgDeal": "avg deal {value}",
1184
+ "customers.deals.list.kpi.currencyApproxMissing": "Approximate; deals in {currencies} without a rate are excluded.",
1185
+ "customers.deals.list.kpi.currencyApproxNoBase": "Approximate; no base currency is configured.",
1186
+ "customers.deals.list.kpi.deltaTooltip": "Compared to previous period",
1187
+ "customers.deals.list.kpi.deltaUnavailable": "No comparable change",
1188
+ "customers.deals.list.kpi.error": "Couldn't load deal metrics",
1189
+ "customers.deals.list.kpi.frag.activeDeals.few": "{count} active deals",
1190
+ "customers.deals.list.kpi.frag.activeDeals.many": "{count} active deals",
1191
+ "customers.deals.list.kpi.frag.activeDeals.one": "{count} active deal",
1192
+ "customers.deals.list.kpi.frag.activeDeals.other": "{count} active deals",
1193
+ "customers.deals.list.kpi.frag.dealsClosed.few": "{count} deals closed this quarter",
1194
+ "customers.deals.list.kpi.frag.dealsClosed.many": "{count} deals closed this quarter",
1195
+ "customers.deals.list.kpi.frag.dealsClosed.one": "{count} deal closed this quarter",
1196
+ "customers.deals.list.kpi.frag.dealsClosed.other": "{count} deals closed this quarter",
1197
+ "customers.deals.list.kpi.frag.needAttention.few": "{count} need attention",
1198
+ "customers.deals.list.kpi.frag.needAttention.many": "{count} need attention",
1199
+ "customers.deals.list.kpi.frag.needAttention.one": "{count} needs attention",
1200
+ "customers.deals.list.kpi.frag.needAttention.other": "{count} need attention",
1201
+ "customers.deals.list.kpi.frag.owners.few": "{count} owners",
1202
+ "customers.deals.list.kpi.frag.owners.many": "{count} owners",
1203
+ "customers.deals.list.kpi.frag.owners.one": "{count} owner",
1204
+ "customers.deals.list.kpi.frag.owners.other": "{count} owners",
1205
+ "customers.deals.list.kpi.frag.pipelines.few": "{count} pipelines",
1206
+ "customers.deals.list.kpi.frag.pipelines.many": "{count} pipelines",
1207
+ "customers.deals.list.kpi.frag.pipelines.one": "{count} pipeline",
1208
+ "customers.deals.list.kpi.frag.pipelines.other": "{count} pipelines",
1209
+ "customers.deals.list.kpi.fromLastQuarter": "from {value}% last quarter",
1210
+ "customers.deals.list.kpi.ownersNeedAttention": "{owners} · {attention}",
1211
+ "customers.deals.list.kpi.pipelineValue": "Pipeline value",
1212
+ "customers.deals.list.kpi.retry": "Retry",
1213
+ "customers.deals.list.kpi.scopeAllPipelinesThisQuarter": "All pipelines · this quarter",
1214
+ "customers.deals.list.kpi.unassignedStage": "Unassigned",
1215
+ "customers.deals.list.kpi.updating": "Updating metrics",
1216
+ "customers.deals.list.kpi.winRate": "Win rate",
1217
+ "customers.deals.list.kpi.wonThisQuarter": "Won this quarter",
1176
1218
  "customers.deals.list.noValue": "Not set",
1177
1219
  "customers.deals.list.refresh": "Refresh",
1178
1220
  "customers.deals.list.searchPlaceholder": "Search by title, description…",
1179
1221
  "customers.deals.list.title": "Deals",
1222
+ "customers.deals.list.unknownOwner": "Unknown owner",
1180
1223
  "customers.deals.list.unnamedCompany": "Unnamed company",
1181
1224
  "customers.deals.list.unnamedPerson": "Unnamed person",
1182
1225
  "customers.deals.pipeline.actions.openDeal": "Open deal",
@@ -1154,6 +1154,9 @@
1154
1154
  "customers.deals.list.bulkDelete.progressName": "Eliminar negocios seleccionados",
1155
1155
  "customers.deals.list.bulkDelete.success": "{count} deals deleted",
1156
1156
  "customers.deals.list.bulkDelete.title": "Delete {count} deals?",
1157
+ "customers.deals.list.close.lost": "Perdido",
1158
+ "customers.deals.list.close.overdue": "Vencido",
1159
+ "customers.deals.list.close.won": "Ganado",
1157
1160
  "customers.deals.list.columns.companies": "Empresas",
1158
1161
  "customers.deals.list.columns.expectedClose": "Cierre esperado",
1159
1162
  "customers.deals.list.columns.owner": "Responsable",
@@ -1171,12 +1174,52 @@
1171
1174
  "customers.deals.list.error.load": "No se pudo cargar la lista de negocios",
1172
1175
  "customers.deals.list.filters.companies": "Empresas",
1173
1176
  "customers.deals.list.filters.companiesPlaceholder": "Filtrar por empresas",
1177
+ "customers.deals.list.filters.needsAttention": "Requiere atención",
1178
+ "customers.deals.list.filters.needsAttentionRemove": "Quitar filtro de requiere atención",
1174
1179
  "customers.deals.list.filters.people": "Personas",
1175
1180
  "customers.deals.list.filters.peoplePlaceholder": "Filtrar por personas",
1181
+ "customers.deals.list.kpi.activeAcrossPipelines": "{deals} en {pipelines}",
1182
+ "customers.deals.list.kpi.activeDeals": "Negocios activos",
1183
+ "customers.deals.list.kpi.avgDeal": "negocio medio {value}",
1184
+ "customers.deals.list.kpi.currencyApproxMissing": "Aproximado; se excluyen los negocios en {currencies} sin tipo de cambio.",
1185
+ "customers.deals.list.kpi.currencyApproxNoBase": "Aproximado; no hay moneda base configurada.",
1186
+ "customers.deals.list.kpi.deltaTooltip": "Comparado con el período anterior",
1187
+ "customers.deals.list.kpi.deltaUnavailable": "Sin cambio comparable",
1188
+ "customers.deals.list.kpi.error": "No se pudieron cargar las métricas",
1189
+ "customers.deals.list.kpi.frag.activeDeals.few": "{count} negocios activos",
1190
+ "customers.deals.list.kpi.frag.activeDeals.many": "{count} negocios activos",
1191
+ "customers.deals.list.kpi.frag.activeDeals.one": "{count} negocio activo",
1192
+ "customers.deals.list.kpi.frag.activeDeals.other": "{count} negocios activos",
1193
+ "customers.deals.list.kpi.frag.dealsClosed.few": "{count} negocios cerrados este trimestre",
1194
+ "customers.deals.list.kpi.frag.dealsClosed.many": "{count} negocios cerrados este trimestre",
1195
+ "customers.deals.list.kpi.frag.dealsClosed.one": "{count} negocio cerrado este trimestre",
1196
+ "customers.deals.list.kpi.frag.dealsClosed.other": "{count} negocios cerrados este trimestre",
1197
+ "customers.deals.list.kpi.frag.needAttention.few": "{count} requieren atención",
1198
+ "customers.deals.list.kpi.frag.needAttention.many": "{count} requieren atención",
1199
+ "customers.deals.list.kpi.frag.needAttention.one": "{count} requiere atención",
1200
+ "customers.deals.list.kpi.frag.needAttention.other": "{count} requieren atención",
1201
+ "customers.deals.list.kpi.frag.owners.few": "{count} responsables",
1202
+ "customers.deals.list.kpi.frag.owners.many": "{count} responsables",
1203
+ "customers.deals.list.kpi.frag.owners.one": "{count} responsable",
1204
+ "customers.deals.list.kpi.frag.owners.other": "{count} responsables",
1205
+ "customers.deals.list.kpi.frag.pipelines.few": "{count} pipelines",
1206
+ "customers.deals.list.kpi.frag.pipelines.many": "{count} pipelines",
1207
+ "customers.deals.list.kpi.frag.pipelines.one": "{count} pipeline",
1208
+ "customers.deals.list.kpi.frag.pipelines.other": "{count} pipelines",
1209
+ "customers.deals.list.kpi.fromLastQuarter": "desde {value}% el trimestre pasado",
1210
+ "customers.deals.list.kpi.ownersNeedAttention": "{owners} · {attention}",
1211
+ "customers.deals.list.kpi.pipelineValue": "Valor del pipeline",
1212
+ "customers.deals.list.kpi.retry": "Reintentar",
1213
+ "customers.deals.list.kpi.scopeAllPipelinesThisQuarter": "Todos los pipelines · este trimestre",
1214
+ "customers.deals.list.kpi.unassignedStage": "Sin asignar",
1215
+ "customers.deals.list.kpi.updating": "Actualizando métricas",
1216
+ "customers.deals.list.kpi.winRate": "Tasa de éxito",
1217
+ "customers.deals.list.kpi.wonThisQuarter": "Ganados este trimestre",
1176
1218
  "customers.deals.list.noValue": "Sin definir",
1177
1219
  "customers.deals.list.refresh": "Actualizar",
1178
1220
  "customers.deals.list.searchPlaceholder": "Buscar negocios…",
1179
1221
  "customers.deals.list.title": "Negocios",
1222
+ "customers.deals.list.unknownOwner": "Responsable desconocido",
1180
1223
  "customers.deals.list.unnamedCompany": "Empresa sin nombre",
1181
1224
  "customers.deals.list.unnamedPerson": "Persona sin nombre",
1182
1225
  "customers.deals.pipeline.actions.openDeal": "Abrir oportunidad",
@@ -1154,6 +1154,9 @@
1154
1154
  "customers.deals.list.bulkDelete.progressName": "Usuń zaznaczone transakcje",
1155
1155
  "customers.deals.list.bulkDelete.success": "{count} deals deleted",
1156
1156
  "customers.deals.list.bulkDelete.title": "Delete {count} deals?",
1157
+ "customers.deals.list.close.lost": "Przegrana",
1158
+ "customers.deals.list.close.overdue": "Po terminie",
1159
+ "customers.deals.list.close.won": "Wygrana",
1157
1160
  "customers.deals.list.columns.companies": "Firmy",
1158
1161
  "customers.deals.list.columns.expectedClose": "Planowana finalizacja",
1159
1162
  "customers.deals.list.columns.owner": "Właściciel",
@@ -1171,12 +1174,52 @@
1171
1174
  "customers.deals.list.error.load": "Nie udało się wczytać listy szans",
1172
1175
  "customers.deals.list.filters.companies": "Firmy",
1173
1176
  "customers.deals.list.filters.companiesPlaceholder": "Filtruj po firmach",
1177
+ "customers.deals.list.filters.needsAttention": "Wymaga uwagi",
1178
+ "customers.deals.list.filters.needsAttentionRemove": "Usuń filtr wymaga uwagi",
1174
1179
  "customers.deals.list.filters.people": "Osoby",
1175
1180
  "customers.deals.list.filters.peoplePlaceholder": "Filtruj po osobach",
1181
+ "customers.deals.list.kpi.activeAcrossPipelines": "{deals} w {pipelines}",
1182
+ "customers.deals.list.kpi.activeDeals": "Aktywne szanse",
1183
+ "customers.deals.list.kpi.avgDeal": "śr. szansa {value}",
1184
+ "customers.deals.list.kpi.currencyApproxMissing": "Wartość przybliżona; szanse w {currencies} bez kursu są pomijane.",
1185
+ "customers.deals.list.kpi.currencyApproxNoBase": "Wartość przybliżona; nie skonfigurowano waluty bazowej.",
1186
+ "customers.deals.list.kpi.deltaTooltip": "W porównaniu z poprzednim okresem",
1187
+ "customers.deals.list.kpi.deltaUnavailable": "Brak porównywalnej zmiany",
1188
+ "customers.deals.list.kpi.error": "Nie udało się załadować metryk",
1189
+ "customers.deals.list.kpi.frag.activeDeals.few": "{count} aktywne szanse",
1190
+ "customers.deals.list.kpi.frag.activeDeals.many": "{count} aktywnych szans",
1191
+ "customers.deals.list.kpi.frag.activeDeals.one": "{count} aktywna szansa",
1192
+ "customers.deals.list.kpi.frag.activeDeals.other": "{count} aktywnych szans",
1193
+ "customers.deals.list.kpi.frag.dealsClosed.few": "{count} szanse zamknięte w tym kwartale",
1194
+ "customers.deals.list.kpi.frag.dealsClosed.many": "{count} szans zamkniętych w tym kwartale",
1195
+ "customers.deals.list.kpi.frag.dealsClosed.one": "{count} szansa zamknięta w tym kwartale",
1196
+ "customers.deals.list.kpi.frag.dealsClosed.other": "{count} szans zamkniętych w tym kwartale",
1197
+ "customers.deals.list.kpi.frag.needAttention.few": "{count} wymagają uwagi",
1198
+ "customers.deals.list.kpi.frag.needAttention.many": "{count} wymaga uwagi",
1199
+ "customers.deals.list.kpi.frag.needAttention.one": "{count} wymaga uwagi",
1200
+ "customers.deals.list.kpi.frag.needAttention.other": "{count} wymaga uwagi",
1201
+ "customers.deals.list.kpi.frag.owners.few": "{count} właścicieli",
1202
+ "customers.deals.list.kpi.frag.owners.many": "{count} właścicieli",
1203
+ "customers.deals.list.kpi.frag.owners.one": "{count} właściciel",
1204
+ "customers.deals.list.kpi.frag.owners.other": "{count} właścicieli",
1205
+ "customers.deals.list.kpi.frag.pipelines.few": "{count} pipeline'y",
1206
+ "customers.deals.list.kpi.frag.pipelines.many": "{count} pipeline'ów",
1207
+ "customers.deals.list.kpi.frag.pipelines.one": "{count} pipeline",
1208
+ "customers.deals.list.kpi.frag.pipelines.other": "{count} pipeline'ów",
1209
+ "customers.deals.list.kpi.fromLastQuarter": "z {value}% w zeszłym kwartale",
1210
+ "customers.deals.list.kpi.ownersNeedAttention": "{owners} · {attention}",
1211
+ "customers.deals.list.kpi.pipelineValue": "Wartość pipeline'u",
1212
+ "customers.deals.list.kpi.retry": "Ponów",
1213
+ "customers.deals.list.kpi.scopeAllPipelinesThisQuarter": "Wszystkie pipeline'y · ten kwartał",
1214
+ "customers.deals.list.kpi.unassignedStage": "Nieprzypisany",
1215
+ "customers.deals.list.kpi.updating": "Aktualizowanie metryk",
1216
+ "customers.deals.list.kpi.winRate": "Skuteczność",
1217
+ "customers.deals.list.kpi.wonThisQuarter": "Wygrane w tym kwartale",
1176
1218
  "customers.deals.list.noValue": "Brak danych",
1177
1219
  "customers.deals.list.refresh": "Odśwież",
1178
1220
  "customers.deals.list.searchPlaceholder": "Szukaj szans…",
1179
1221
  "customers.deals.list.title": "Szanse",
1222
+ "customers.deals.list.unknownOwner": "Nieznany właściciel",
1180
1223
  "customers.deals.list.unnamedCompany": "Firma bez nazwy",
1181
1224
  "customers.deals.list.unnamedPerson": "Osoba bez nazwy",
1182
1225
  "customers.deals.pipeline.actions.openDeal": "Otwórz szansę",