@startsimpli/ui 0.4.6 → 0.4.8

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 (122) hide show
  1. package/package.json +2 -1
  2. package/src/__mocks__/next/link.js +11 -0
  3. package/src/components/ActivityTimeline.tsx +173 -0
  4. package/src/components/LogActivityDialog.tsx +303 -0
  5. package/src/components/QuickLogButtons.tsx +32 -0
  6. package/src/components/account/__tests__/account.test.tsx +315 -0
  7. package/src/components/badge/StageBadge.tsx +31 -0
  8. package/src/components/badge/index.ts +3 -0
  9. package/src/components/command-palette/CommandGroup.tsx +23 -0
  10. package/src/components/command-palette/CommandPalette.tsx +327 -0
  11. package/src/components/command-palette/CommandResultItem.tsx +59 -0
  12. package/src/components/command-palette/__tests__/CommandGroup.test.tsx +81 -0
  13. package/src/components/command-palette/__tests__/CommandResultItem.test.tsx +166 -0
  14. package/src/components/command-palette/__tests__/command-palette-context.test.tsx +166 -0
  15. package/src/components/command-palette/__tests__/useCommandPaletteSearch.test.ts +271 -0
  16. package/src/components/command-palette/command-palette-context.tsx +51 -0
  17. package/src/components/command-palette/index.ts +9 -0
  18. package/src/components/command-palette/useCommandPaletteSearch.ts +114 -0
  19. package/src/components/compose/__tests__/compose.test.tsx +656 -0
  20. package/src/components/compose/compose-header.tsx +72 -0
  21. package/src/components/compose/compose-loading.tsx +13 -0
  22. package/src/components/compose/index.ts +6 -0
  23. package/src/components/compose/save-status-indicator.tsx +57 -0
  24. package/src/components/compose/send-confirmation-dialog.tsx +87 -0
  25. package/src/components/compose/subject-input.tsx +25 -0
  26. package/src/components/compose/useAutoSave.ts +93 -0
  27. package/src/components/dashboard/DashboardGrid.tsx +32 -0
  28. package/src/components/dashboard/DashboardSection.tsx +32 -0
  29. package/src/components/dashboard/MetricCard.tsx +129 -0
  30. package/src/components/dashboard/PeriodSelector.tsx +55 -0
  31. package/src/components/dashboard/PipelineFunnel.tsx +126 -0
  32. package/src/components/dashboard/SparklineTrend.tsx +102 -0
  33. package/src/components/dashboard/TopCampaigns.tsx +132 -0
  34. package/src/components/dashboard/__tests__/dashboard.test.tsx +785 -0
  35. package/src/components/dashboard/index.ts +20 -0
  36. package/src/components/dialog/ConfirmDialog.tsx +72 -0
  37. package/src/components/dialog/__tests__/ConfirmDialog.test.tsx +126 -0
  38. package/src/components/dialog/index.ts +3 -0
  39. package/src/components/email-dialogs/__tests__/email-dialogs.test.tsx +982 -0
  40. package/src/components/email-dialogs/index.ts +14 -0
  41. package/src/components/email-dialogs/merge-fields.tsx +196 -0
  42. package/src/components/email-dialogs/preview-dialog.tsx +194 -0
  43. package/src/components/email-dialogs/schedule-dialog.tsx +297 -0
  44. package/src/components/email-dialogs/template-picker.tsx +225 -0
  45. package/src/components/email-dialogs/test-send-dialog.tsx +188 -0
  46. package/src/components/email-editor/BlockRenderer.tsx +120 -0
  47. package/src/components/email-editor/__tests__/BlockRenderer.test.tsx +332 -0
  48. package/src/components/email-editor/__tests__/block-renderers.test.ts +624 -0
  49. package/src/components/email-editor/__tests__/email-html-renderer.test.ts +376 -0
  50. package/src/components/email-editor/add-block-menu.tsx +151 -0
  51. package/src/components/email-editor/block-toolbar.tsx +73 -0
  52. package/src/components/email-editor/blocks/__tests__/blocks.test.tsx +818 -0
  53. package/src/components/email-editor/blocks/button-block.tsx +44 -0
  54. package/src/components/email-editor/blocks/divider-block.tsx +43 -0
  55. package/src/components/email-editor/blocks/footer-block.tsx +39 -0
  56. package/src/components/email-editor/blocks/header-block.tsx +39 -0
  57. package/src/components/email-editor/blocks/image-block.tsx +61 -0
  58. package/src/components/email-editor/blocks/index.ts +9 -0
  59. package/src/components/email-editor/blocks/metrics-block.tsx +198 -0
  60. package/src/components/email-editor/blocks/social-block.tsx +75 -0
  61. package/src/components/email-editor/blocks/spacer-block.tsx +26 -0
  62. package/src/components/email-editor/blocks/text-block.tsx +75 -0
  63. package/src/components/email-editor/editor-sidebar.tsx +66 -0
  64. package/src/components/email-editor/email-editor.tsx +497 -0
  65. package/src/components/email-editor/hooks/__tests__/useDragDrop.test.ts +355 -0
  66. package/src/components/email-editor/hooks/__tests__/useEmailEditorState.test.ts +551 -0
  67. package/src/components/email-editor/hooks/useDragDrop.ts +181 -0
  68. package/src/components/email-editor/hooks/useEmailEditorState.ts +426 -0
  69. package/src/components/email-editor/index.ts +51 -0
  70. package/src/components/email-editor/panels/BlockPropertyPanel.tsx +637 -0
  71. package/src/components/email-editor/panels/GlobalStylesPanel.tsx +108 -0
  72. package/src/components/email-editor/panels/SectionSettingsPanel.tsx +80 -0
  73. package/src/components/email-editor/panels/__tests__/BlockPropertyPanel.test.tsx +707 -0
  74. package/src/components/email-editor/panels/__tests__/GlobalStylesPanel.test.tsx +226 -0
  75. package/src/components/email-editor/panels/index.ts +3 -0
  76. package/src/components/email-editor/renderer/block-renderers.ts +209 -0
  77. package/src/components/email-editor/renderer/email-html-renderer.ts +128 -0
  78. package/src/components/email-editor/types.ts +413 -0
  79. package/src/components/email-editor/utils/defaults.ts +116 -0
  80. package/src/components/email-editor/utils/undo-redo.ts +59 -0
  81. package/src/components/enrichment/EnrichButton.tsx +33 -0
  82. package/src/components/enrichment/EnrichmentProgress.tsx +66 -0
  83. package/src/components/enrichment/QualityBadge.tsx +43 -0
  84. package/src/components/enrichment/__tests__/enrichment.test.tsx +184 -0
  85. package/src/components/enrichment/index.ts +8 -0
  86. package/src/components/gantt/GanttBoardView.tsx +71 -0
  87. package/src/components/gantt/GanttChart.tsx +140 -887
  88. package/src/components/gantt/GanttFilterBar.tsx +100 -0
  89. package/src/components/gantt/GanttListView.tsx +63 -0
  90. package/src/components/gantt/GanttTimelineView.tsx +215 -0
  91. package/src/components/gantt/__tests__/GanttBoardView.test.tsx +305 -0
  92. package/src/components/gantt/__tests__/GanttFilterBar.test.tsx +544 -0
  93. package/src/components/gantt/__tests__/GanttListView.test.tsx +337 -0
  94. package/src/components/gantt/__tests__/GanttTimelineView.test.tsx +375 -0
  95. package/src/components/gantt/__tests__/gantt-utils.test.ts +341 -0
  96. package/src/components/gantt/__tests__/useGanttState.test.ts +535 -0
  97. package/src/components/gantt/hooks/useGanttState.ts +644 -0
  98. package/src/components/gantt/index.ts +10 -0
  99. package/src/components/gantt/types.ts +5 -5
  100. package/src/components/index.ts +46 -0
  101. package/src/components/integrations/ConnectionStatus.tsx +77 -0
  102. package/src/components/integrations/IntegrationCard.tsx +92 -0
  103. package/src/components/integrations/__tests__/integrations.test.tsx +191 -0
  104. package/src/components/integrations/index.ts +5 -0
  105. package/src/components/kanban/KanbanBoard.tsx +103 -0
  106. package/src/components/kanban/__tests__/kanban.test.tsx +157 -0
  107. package/src/components/kanban/index.ts +2 -0
  108. package/src/components/lists/CreateListDialog.tsx +158 -0
  109. package/src/components/lists/ListCard.tsx +77 -0
  110. package/src/components/lists/__tests__/lists.test.tsx +263 -0
  111. package/src/components/lists/index.ts +5 -0
  112. package/src/components/loading/__tests__/loading.test.tsx +114 -0
  113. package/src/components/navigation/__tests__/navigation.test.tsx +194 -0
  114. package/src/components/pipeline/StageTransitionModal.tsx +146 -0
  115. package/src/components/pipeline/__tests__/pipeline.test.tsx +169 -0
  116. package/src/components/pipeline/index.ts +2 -0
  117. package/src/components/settings/SettingsCard.tsx +33 -0
  118. package/src/components/settings/SettingsLayout.tsx +28 -0
  119. package/src/components/settings/SettingsNav.tsx +42 -0
  120. package/src/components/settings/__tests__/settings.test.tsx +181 -0
  121. package/src/components/settings/index.ts +6 -0
  122. package/src/components/wizard/__tests__/wizard.test.tsx +97 -0
@@ -0,0 +1,43 @@
1
+ import * as React from 'react'
2
+
3
+ export interface QualityScore {
4
+ score: number
5
+ grade: 'A' | 'B' | 'C' | 'D' | 'F'
6
+ }
7
+
8
+ export interface QualityBadgeProps {
9
+ quality: QualityScore
10
+ showScore?: boolean
11
+ }
12
+
13
+ const GRADE_STYLES: Record<QualityScore['grade'], string> = {
14
+ A: 'bg-green-100 text-green-800 border-green-200',
15
+ B: 'bg-blue-100 text-blue-800 border-blue-200',
16
+ C: 'bg-yellow-100 text-yellow-800 border-yellow-200',
17
+ D: 'bg-orange-100 text-orange-800 border-orange-200',
18
+ F: 'bg-red-100 text-red-800 border-red-200',
19
+ }
20
+
21
+ const GRADE_LABELS: Record<QualityScore['grade'], string> = {
22
+ A: 'Excellent',
23
+ B: 'Good',
24
+ C: 'Fair',
25
+ D: 'Poor',
26
+ F: 'Incomplete',
27
+ }
28
+
29
+ export function QualityBadge({ quality, showScore = false }: QualityBadgeProps) {
30
+ const { grade, score } = quality
31
+ const styleClass = GRADE_STYLES[grade]
32
+ const label = GRADE_LABELS[grade]
33
+
34
+ return (
35
+ <span
36
+ className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold border ${styleClass}`}
37
+ title={`Data quality: ${label} (${score}/100)`}
38
+ >
39
+ <span>{grade}</span>
40
+ {showScore && <span className="font-normal opacity-75">({score})</span>}
41
+ </span>
42
+ )
43
+ }
@@ -0,0 +1,184 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react'
2
+ import { QualityBadge } from '../QualityBadge'
3
+ import { EnrichButton } from '../EnrichButton'
4
+ import { EnrichmentProgress } from '../EnrichmentProgress'
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // QualityBadge
8
+ // ---------------------------------------------------------------------------
9
+
10
+ describe('QualityBadge', () => {
11
+ it('renders grade A with correct label in title', () => {
12
+ render(<QualityBadge quality={{ grade: 'A', score: 95 }} />)
13
+ const badge = screen.getByTitle('Data quality: Excellent (95/100)')
14
+ expect(badge).toBeInTheDocument()
15
+ expect(badge).toHaveTextContent('A')
16
+ })
17
+
18
+ it('renders grade B with correct title', () => {
19
+ render(<QualityBadge quality={{ grade: 'B', score: 80 }} />)
20
+ expect(screen.getByTitle('Data quality: Good (80/100)')).toBeInTheDocument()
21
+ })
22
+
23
+ it('renders grade C with correct title', () => {
24
+ render(<QualityBadge quality={{ grade: 'C', score: 65 }} />)
25
+ expect(screen.getByTitle('Data quality: Fair (65/100)')).toBeInTheDocument()
26
+ })
27
+
28
+ it('renders grade D with correct title', () => {
29
+ render(<QualityBadge quality={{ grade: 'D', score: 45 }} />)
30
+ expect(screen.getByTitle('Data quality: Poor (45/100)')).toBeInTheDocument()
31
+ })
32
+
33
+ it('renders grade F with correct title', () => {
34
+ render(<QualityBadge quality={{ grade: 'F', score: 20 }} />)
35
+ expect(screen.getByTitle('Data quality: Incomplete (20/100)')).toBeInTheDocument()
36
+ })
37
+
38
+ it('does not show score by default', () => {
39
+ render(<QualityBadge quality={{ grade: 'A', score: 95 }} />)
40
+ expect(screen.queryByText('(95)')).not.toBeInTheDocument()
41
+ })
42
+
43
+ it('shows score when showScore is true', () => {
44
+ render(<QualityBadge quality={{ grade: 'A', score: 95 }} showScore />)
45
+ expect(screen.getByText('(95)')).toBeInTheDocument()
46
+ })
47
+
48
+ it('applies green styles for grade A', () => {
49
+ render(<QualityBadge quality={{ grade: 'A', score: 95 }} />)
50
+ const badge = screen.getByTitle('Data quality: Excellent (95/100)')
51
+ expect(badge).toHaveClass('bg-green-100')
52
+ expect(badge).toHaveClass('text-green-800')
53
+ })
54
+
55
+ it('applies red styles for grade F', () => {
56
+ render(<QualityBadge quality={{ grade: 'F', score: 20 }} />)
57
+ const badge = screen.getByTitle('Data quality: Incomplete (20/100)')
58
+ expect(badge).toHaveClass('bg-red-100')
59
+ expect(badge).toHaveClass('text-red-800')
60
+ })
61
+
62
+ it('applies yellow styles for grade C', () => {
63
+ render(<QualityBadge quality={{ grade: 'C', score: 65 }} />)
64
+ const badge = screen.getByTitle('Data quality: Fair (65/100)')
65
+ expect(badge).toHaveClass('bg-yellow-100')
66
+ })
67
+ })
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // EnrichButton
71
+ // ---------------------------------------------------------------------------
72
+
73
+ describe('EnrichButton', () => {
74
+ it('renders default label', () => {
75
+ render(<EnrichButton onClick={jest.fn()} />)
76
+ expect(screen.getByRole('button', { name: /enrich/i })).toBeInTheDocument()
77
+ })
78
+
79
+ it('renders custom label', () => {
80
+ render(<EnrichButton onClick={jest.fn()} label="Run Enrichment" />)
81
+ expect(screen.getByRole('button', { name: /run enrichment/i })).toBeInTheDocument()
82
+ })
83
+
84
+ it('shows Enriching... when isLoading is true', () => {
85
+ render(<EnrichButton onClick={jest.fn()} isLoading />)
86
+ expect(screen.getByText('Enriching...')).toBeInTheDocument()
87
+ })
88
+
89
+ it('is disabled when isLoading is true', () => {
90
+ render(<EnrichButton onClick={jest.fn()} isLoading />)
91
+ expect(screen.getByRole('button')).toBeDisabled()
92
+ })
93
+
94
+ it('calls onClick when clicked', () => {
95
+ const onClick = jest.fn()
96
+ render(<EnrichButton onClick={onClick} />)
97
+ fireEvent.click(screen.getByRole('button'))
98
+ expect(onClick).toHaveBeenCalledTimes(1)
99
+ })
100
+
101
+ it('does not call onClick when disabled', () => {
102
+ const onClick = jest.fn()
103
+ render(<EnrichButton onClick={onClick} isLoading />)
104
+ fireEvent.click(screen.getByRole('button'))
105
+ expect(onClick).not.toHaveBeenCalled()
106
+ })
107
+
108
+ it('applies sm size classes by default', () => {
109
+ render(<EnrichButton onClick={jest.fn()} />)
110
+ expect(screen.getByRole('button')).toHaveClass('px-3', 'py-1.5', 'text-xs')
111
+ })
112
+
113
+ it('applies md size classes when size is md', () => {
114
+ render(<EnrichButton onClick={jest.fn()} size="md" />)
115
+ expect(screen.getByRole('button')).toHaveClass('px-4', 'py-2', 'text-sm')
116
+ })
117
+
118
+ it('applies custom className', () => {
119
+ render(<EnrichButton onClick={jest.fn()} className="my-custom-class" />)
120
+ expect(screen.getByRole('button')).toHaveClass('my-custom-class')
121
+ })
122
+ })
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // EnrichmentProgress
126
+ // ---------------------------------------------------------------------------
127
+
128
+ describe('EnrichmentProgress', () => {
129
+ const queueStatus = {
130
+ pending: 3,
131
+ processing: 1,
132
+ completed: 12,
133
+ failed: 0,
134
+ }
135
+
136
+ it('renders unavailable message when queueStatus is null', () => {
137
+ render(<EnrichmentProgress queueStatus={null} />)
138
+ expect(screen.getByText('Queue status unavailable')).toBeInTheDocument()
139
+ })
140
+
141
+ it('renders all four status counts', () => {
142
+ render(<EnrichmentProgress queueStatus={queueStatus} />)
143
+ expect(screen.getByText('3')).toBeInTheDocument() // pending
144
+ expect(screen.getByText('1')).toBeInTheDocument() // processing
145
+ expect(screen.getByText('12')).toBeInTheDocument() // completed
146
+ expect(screen.getByText('0')).toBeInTheDocument() // failed
147
+ })
148
+
149
+ it('renders all four status labels', () => {
150
+ render(<EnrichmentProgress queueStatus={queueStatus} />)
151
+ expect(screen.getByText('Pending')).toBeInTheDocument()
152
+ expect(screen.getByText('Processing')).toBeInTheDocument()
153
+ expect(screen.getByText('Completed')).toBeInTheDocument()
154
+ expect(screen.getByText('Failed')).toBeInTheDocument()
155
+ })
156
+
157
+ it('does not render refresh button when onRefresh is not provided', () => {
158
+ render(<EnrichmentProgress queueStatus={queueStatus} />)
159
+ expect(screen.queryByText('Refresh')).not.toBeInTheDocument()
160
+ })
161
+
162
+ it('renders refresh button when onRefresh is provided', () => {
163
+ render(<EnrichmentProgress queueStatus={queueStatus} onRefresh={jest.fn()} />)
164
+ expect(screen.getByText('Refresh')).toBeInTheDocument()
165
+ })
166
+
167
+ it('calls onRefresh when refresh button is clicked', () => {
168
+ const onRefresh = jest.fn()
169
+ render(<EnrichmentProgress queueStatus={queueStatus} onRefresh={onRefresh} />)
170
+ fireEvent.click(screen.getByText('Refresh'))
171
+ expect(onRefresh).toHaveBeenCalledTimes(1)
172
+ })
173
+
174
+ it('disables refresh button when isLoading is true', () => {
175
+ render(
176
+ <EnrichmentProgress
177
+ queueStatus={queueStatus}
178
+ onRefresh={jest.fn()}
179
+ isLoading
180
+ />
181
+ )
182
+ expect(screen.getByText('Refresh').closest('button')).toBeDisabled()
183
+ })
184
+ })
@@ -0,0 +1,8 @@
1
+ export { QualityBadge } from './QualityBadge'
2
+ export type { QualityBadgeProps, QualityScore } from './QualityBadge'
3
+
4
+ export { EnrichButton } from './EnrichButton'
5
+ export type { EnrichButtonProps } from './EnrichButton'
6
+
7
+ export { EnrichmentProgress } from './EnrichmentProgress'
8
+ export type { EnrichmentProgressProps, QueueStatus as EnrichmentQueueStatus } from './EnrichmentProgress'
@@ -0,0 +1,71 @@
1
+ 'use client'
2
+
3
+ import { format } from 'date-fns'
4
+ import type { TimelineItem } from './types'
5
+ import type { UseGanttStateReturn } from './hooks/useGanttState'
6
+
7
+ export interface GanttBoardViewProps {
8
+ boardColumns: UseGanttStateReturn['boardColumns']
9
+ boardDragItem: UseGanttStateReturn['boardDragItem']
10
+ setBoardDragItem: UseGanttStateReturn['setBoardDragItem']
11
+ onStatusChange: UseGanttStateReturn['onStatusChange']
12
+ categoryColors: UseGanttStateReturn['categoryColors']
13
+ handleItemClick: UseGanttStateReturn['handleItemClick']
14
+ }
15
+
16
+ export function GanttBoardView({
17
+ boardColumns,
18
+ boardDragItem,
19
+ setBoardDragItem,
20
+ onStatusChange,
21
+ categoryColors,
22
+ handleItemClick,
23
+ }: GanttBoardViewProps) {
24
+ return (
25
+ <div className="gantt-board">
26
+ {boardColumns.map((col) => (
27
+ <div
28
+ key={col.status}
29
+ className={`gantt-board-column ${boardDragItem ? 'gantt-board-drop-target' : ''}`}
30
+ onDragOver={(e) => { e.preventDefault(); e.currentTarget.classList.add('gantt-board-drag-over') }}
31
+ onDragLeave={(e) => { e.currentTarget.classList.remove('gantt-board-drag-over') }}
32
+ onDrop={(e) => {
33
+ e.preventDefault()
34
+ e.currentTarget.classList.remove('gantt-board-drag-over')
35
+ if (boardDragItem && onStatusChange) {
36
+ onStatusChange(boardDragItem, col.status)
37
+ }
38
+ setBoardDragItem(null)
39
+ }}
40
+ >
41
+ <div className="gantt-board-column-header">
42
+ <span className={`gantt-status-badge gantt-status-${col.status}`}>{col.status.replace(/_/g, ' ')}</span>
43
+ <span className="gantt-board-count">{col.items.length}</span>
44
+ </div>
45
+ <div className="gantt-board-cards">
46
+ {col.items.map((item) => (
47
+ <div
48
+ key={item.id}
49
+ className="gantt-board-card"
50
+ draggable={!!onStatusChange}
51
+ onDragStart={() => setBoardDragItem(item.id)}
52
+ onDragEnd={() => setBoardDragItem(null)}
53
+ onClick={() => handleItemClick(item)}
54
+ >
55
+ <div className="gantt-board-card-title">{item.title}</div>
56
+ {item.category && (
57
+ <span className="gantt-category-badge" style={{ backgroundColor: categoryColors[item.category] || categoryColors.other || '#6b7280' }}>
58
+ {item.category}
59
+ </span>
60
+ )}
61
+ {item.endDate && (
62
+ <span className="gantt-board-card-date">{format(new Date(item.endDate), 'MMM d')}</span>
63
+ )}
64
+ </div>
65
+ ))}
66
+ </div>
67
+ </div>
68
+ ))}
69
+ </div>
70
+ )
71
+ }