@papernote/ui 1.10.26 → 1.11.1
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/dist/components/ActionCard.d.ts +48 -0
- package/dist/components/ActionCard.d.ts.map +1 -0
- package/dist/components/AnomalyBanner.d.ts +27 -0
- package/dist/components/AnomalyBanner.d.ts.map +1 -0
- package/dist/components/CaseQueueItem.d.ts +35 -0
- package/dist/components/CaseQueueItem.d.ts.map +1 -0
- package/dist/components/ConfidenceBadge.d.ts +19 -0
- package/dist/components/ConfidenceBadge.d.ts.map +1 -0
- package/dist/components/ConfidenceIndicator.d.ts +25 -0
- package/dist/components/ConfidenceIndicator.d.ts.map +1 -0
- package/dist/components/EntityCard.d.ts +46 -0
- package/dist/components/EntityCard.d.ts.map +1 -0
- package/dist/components/FunnelChart.d.ts +31 -0
- package/dist/components/FunnelChart.d.ts.map +1 -0
- package/dist/components/MatchIndicator.d.ts +20 -0
- package/dist/components/MatchIndicator.d.ts.map +1 -0
- package/dist/components/PageLayout.d.ts.map +1 -1
- package/dist/components/PersonaDashboard.d.ts +39 -0
- package/dist/components/PersonaDashboard.d.ts.map +1 -0
- package/dist/components/ProcessHealthBar.d.ts +28 -0
- package/dist/components/ProcessHealthBar.d.ts.map +1 -0
- package/dist/components/ProcessIndicator.d.ts +38 -0
- package/dist/components/ProcessIndicator.d.ts.map +1 -0
- package/dist/components/ReviewDecisionCard.d.ts +53 -0
- package/dist/components/ReviewDecisionCard.d.ts.map +1 -0
- package/dist/components/SLAIndicator.d.ts +24 -0
- package/dist/components/SLAIndicator.d.ts.map +1 -0
- package/dist/components/SplitPane.d.ts +33 -0
- package/dist/components/SplitPane.d.ts.map +1 -0
- package/dist/components/SystemActionEntry.d.ts +42 -0
- package/dist/components/SystemActionEntry.d.ts.map +1 -0
- package/dist/components/VarianceDisplay.d.ts +26 -0
- package/dist/components/VarianceDisplay.d.ts.map +1 -0
- package/dist/components/index.d.ts +32 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.ts +529 -2
- package/dist/index.esm.js +664 -31
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +678 -29
- package/dist/index.js.map +1 -1
- package/dist/styles.css +367 -10
- package/package.json +1 -1
- package/src/components/ActionCard.tsx +176 -0
- package/src/components/AnomalyBanner.tsx +113 -0
- package/src/components/CaseQueueItem.tsx +145 -0
- package/src/components/ConfidenceBadge.tsx +62 -0
- package/src/components/ConfidenceIndicator.tsx +96 -0
- package/src/components/EntityCard.tsx +216 -0
- package/src/components/FunnelChart.tsx +160 -0
- package/src/components/MatchIndicator.tsx +73 -0
- package/src/components/Page.tsx +2 -2
- package/src/components/PageLayout.tsx +1 -1
- package/src/components/PersonaDashboard.tsx +105 -0
- package/src/components/ProcessHealthBar.tsx +107 -0
- package/src/components/ProcessIndicator.tsx +167 -0
- package/src/components/ReviewDecisionCard.tsx +186 -0
- package/src/components/SLAIndicator.tsx +108 -0
- package/src/components/SplitPane.tsx +150 -0
- package/src/components/SystemActionEntry.tsx +175 -0
- package/src/components/VarianceDisplay.tsx +116 -0
- package/src/components/index.ts +48 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Types & Interfaces
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
export interface ActionCardAction {
|
|
8
|
+
/** Button label */
|
|
9
|
+
label: string;
|
|
10
|
+
/** Button variant */
|
|
11
|
+
variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
|
|
12
|
+
/** Click handler */
|
|
13
|
+
onClick: () => void;
|
|
14
|
+
/** Whether the action is disabled */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/** Icon to show */
|
|
17
|
+
icon?: React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ActionCardProps {
|
|
21
|
+
/** Card title — what needs action */
|
|
22
|
+
title: string;
|
|
23
|
+
/** Description — why the system flagged it */
|
|
24
|
+
description?: string;
|
|
25
|
+
/** Affected entity type and ID */
|
|
26
|
+
entityType?: string;
|
|
27
|
+
entityId?: string;
|
|
28
|
+
/** Entity link click handler */
|
|
29
|
+
onEntityClick?: () => void;
|
|
30
|
+
/** Reasoning text — why the system flagged this */
|
|
31
|
+
reasoning?: string;
|
|
32
|
+
/** Available actions */
|
|
33
|
+
actions: ActionCardAction[];
|
|
34
|
+
/** Priority/urgency level */
|
|
35
|
+
priority?: 'high' | 'medium' | 'low';
|
|
36
|
+
/** Icon */
|
|
37
|
+
icon?: React.ReactNode;
|
|
38
|
+
/** Confidence score (0-100) */
|
|
39
|
+
confidence?: number;
|
|
40
|
+
/** Timestamp */
|
|
41
|
+
timestamp?: string;
|
|
42
|
+
/** Whether the card is selected */
|
|
43
|
+
selected?: boolean;
|
|
44
|
+
/** Additional className */
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// Helpers
|
|
50
|
+
// =============================================================================
|
|
51
|
+
|
|
52
|
+
const priorityStyles: Record<string, { border: string; dot: string }> = {
|
|
53
|
+
high: { border: 'border-l-4 border-l-red-400', dot: 'bg-red-500' },
|
|
54
|
+
medium: { border: 'border-l-4 border-l-yellow-400', dot: 'bg-yellow-500' },
|
|
55
|
+
low: { border: 'border-l-4 border-l-blue-400', dot: 'bg-blue-500' },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const buttonVariants: Record<string, string> = {
|
|
59
|
+
primary: 'bg-primary-600 text-white hover:bg-primary-700',
|
|
60
|
+
secondary: 'bg-paper-100 text-ink-700 hover:bg-paper-200 border border-paper-300',
|
|
61
|
+
danger: 'bg-error-600 text-white hover:bg-error-700',
|
|
62
|
+
ghost: 'text-ink-600 hover:bg-paper-100',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Component
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* ActionCard — Approval workflow card with action buttons
|
|
71
|
+
*
|
|
72
|
+
* Shows what needs action, why it was flagged, and provides quick-action buttons.
|
|
73
|
+
* Used in the Approvals & Review queue across all process domains.
|
|
74
|
+
*/
|
|
75
|
+
export default function ActionCard({
|
|
76
|
+
title,
|
|
77
|
+
description,
|
|
78
|
+
entityType,
|
|
79
|
+
entityId,
|
|
80
|
+
onEntityClick,
|
|
81
|
+
reasoning,
|
|
82
|
+
actions,
|
|
83
|
+
priority,
|
|
84
|
+
icon,
|
|
85
|
+
confidence,
|
|
86
|
+
timestamp,
|
|
87
|
+
selected = false,
|
|
88
|
+
className = '',
|
|
89
|
+
}: ActionCardProps) {
|
|
90
|
+
const prioStyle = priority ? priorityStyles[priority] : undefined;
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div
|
|
94
|
+
className={`
|
|
95
|
+
rounded-lg border bg-white dark:bg-ink-900 p-4
|
|
96
|
+
${selected ? 'border-primary-400 ring-2 ring-primary-100' : 'border-paper-200 dark:border-ink-700'}
|
|
97
|
+
${prioStyle?.border || ''}
|
|
98
|
+
${className}
|
|
99
|
+
`}
|
|
100
|
+
>
|
|
101
|
+
{/* Header: icon + title + confidence */}
|
|
102
|
+
<div className="flex items-start gap-3 mb-3">
|
|
103
|
+
{icon && (
|
|
104
|
+
<div className="flex-shrink-0 text-ink-400 mt-0.5">{icon}</div>
|
|
105
|
+
)}
|
|
106
|
+
<div className="flex-1 min-w-0">
|
|
107
|
+
<div className="flex items-center gap-2">
|
|
108
|
+
<h4 className="text-sm font-medium text-ink-900 dark:text-ink-100">
|
|
109
|
+
{title}
|
|
110
|
+
</h4>
|
|
111
|
+
{priority && prioStyle && (
|
|
112
|
+
<span className={`w-2 h-2 rounded-full ${prioStyle.dot} flex-shrink-0`} />
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
{description && (
|
|
116
|
+
<p className="text-sm text-ink-500 mt-0.5">{description}</p>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
{confidence !== undefined && (
|
|
120
|
+
<span className={`text-xs px-2 py-0.5 rounded flex-shrink-0 ${
|
|
121
|
+
confidence >= 80 ? 'bg-success-50 text-success-700' :
|
|
122
|
+
confidence >= 50 ? 'bg-warning-50 text-warning-700' :
|
|
123
|
+
'bg-error-50 text-error-700'
|
|
124
|
+
}`}>
|
|
125
|
+
{confidence}% confidence
|
|
126
|
+
</span>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Entity link */}
|
|
131
|
+
{entityType && entityId && (
|
|
132
|
+
<div className="mb-2">
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
onClick={onEntityClick}
|
|
136
|
+
className="text-xs text-primary-600 hover:text-primary-700 hover:underline"
|
|
137
|
+
>
|
|
138
|
+
{entityType} #{entityId}
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
{/* Reasoning */}
|
|
144
|
+
{reasoning && (
|
|
145
|
+
<div className="mb-3 px-3 py-2 bg-paper-50 dark:bg-ink-800 rounded text-xs text-ink-600 dark:text-ink-400 border-l-2 border-ink-200">
|
|
146
|
+
{reasoning}
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{/* Actions + timestamp */}
|
|
151
|
+
<div className="flex items-center justify-between gap-2">
|
|
152
|
+
<div className="flex items-center gap-2">
|
|
153
|
+
{actions.map((action, idx) => (
|
|
154
|
+
<button
|
|
155
|
+
key={idx}
|
|
156
|
+
type="button"
|
|
157
|
+
onClick={action.onClick}
|
|
158
|
+
disabled={action.disabled}
|
|
159
|
+
className={`
|
|
160
|
+
inline-flex items-center gap-1 px-3 py-1.5 rounded text-xs font-medium
|
|
161
|
+
transition-colors disabled:opacity-50 disabled:cursor-not-allowed
|
|
162
|
+
${buttonVariants[action.variant || 'secondary']}
|
|
163
|
+
`}
|
|
164
|
+
>
|
|
165
|
+
{action.icon}
|
|
166
|
+
{action.label}
|
|
167
|
+
</button>
|
|
168
|
+
))}
|
|
169
|
+
</div>
|
|
170
|
+
{timestamp && (
|
|
171
|
+
<span className="text-xs text-ink-400 flex-shrink-0">{timestamp}</span>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { AlertTriangle, X, ArrowRight } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Types & Interfaces
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
export interface AnomalyBannerProps {
|
|
8
|
+
/** Anomaly title/summary */
|
|
9
|
+
title: string;
|
|
10
|
+
/** Detailed description */
|
|
11
|
+
description?: string;
|
|
12
|
+
/** Severity level */
|
|
13
|
+
severity?: 'info' | 'warning' | 'critical';
|
|
14
|
+
/** Affected entity count */
|
|
15
|
+
affectedCount?: number;
|
|
16
|
+
/** Action button label */
|
|
17
|
+
actionLabel?: string;
|
|
18
|
+
/** Action button handler */
|
|
19
|
+
onAction?: () => void;
|
|
20
|
+
/** Dismiss handler */
|
|
21
|
+
onDismiss?: () => void;
|
|
22
|
+
/** Additional className */
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Helpers
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
const severityConfig: Record<string, { bg: string; border: string; text: string; icon: string }> = {
|
|
31
|
+
info: { bg: 'bg-blue-50 dark:bg-blue-900/20', border: 'border-blue-200 dark:border-blue-800', text: 'text-blue-800 dark:text-blue-200', icon: 'text-blue-500' },
|
|
32
|
+
warning: { bg: 'bg-warning-50 dark:bg-warning-900/20', border: 'border-warning-200 dark:border-warning-800', text: 'text-warning-800 dark:text-warning-200', icon: 'text-warning-500' },
|
|
33
|
+
critical: { bg: 'bg-error-50 dark:bg-error-900/20', border: 'border-error-200 dark:border-error-800', text: 'text-error-800 dark:text-error-200', icon: 'text-error-500' },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Component
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* AnomalyBanner — Alert banner for anomaly detection
|
|
42
|
+
*
|
|
43
|
+
* Shows a prominent banner when the system detects unusual patterns.
|
|
44
|
+
* Severity levels: info (FYI), warning (review recommended), critical (immediate action).
|
|
45
|
+
* Used at the top of process views and dashboards.
|
|
46
|
+
*/
|
|
47
|
+
export default function AnomalyBanner({
|
|
48
|
+
title,
|
|
49
|
+
description,
|
|
50
|
+
severity = 'warning',
|
|
51
|
+
affectedCount,
|
|
52
|
+
actionLabel,
|
|
53
|
+
onAction,
|
|
54
|
+
onDismiss,
|
|
55
|
+
className = '',
|
|
56
|
+
}: AnomalyBannerProps) {
|
|
57
|
+
const config = severityConfig[severity];
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
className={`
|
|
62
|
+
flex items-start gap-3 px-4 py-3 rounded-lg border
|
|
63
|
+
${config.bg} ${config.border}
|
|
64
|
+
${className}
|
|
65
|
+
`}
|
|
66
|
+
role="alert"
|
|
67
|
+
>
|
|
68
|
+
{/* Icon */}
|
|
69
|
+
<AlertTriangle className={`h-5 w-5 flex-shrink-0 mt-0.5 ${config.icon}`} />
|
|
70
|
+
|
|
71
|
+
{/* Content */}
|
|
72
|
+
<div className="flex-1 min-w-0">
|
|
73
|
+
<div className="flex items-center gap-2">
|
|
74
|
+
<h4 className={`text-sm font-medium ${config.text}`}>
|
|
75
|
+
{title}
|
|
76
|
+
</h4>
|
|
77
|
+
{affectedCount !== undefined && (
|
|
78
|
+
<span className={`text-xs px-1.5 py-0.5 rounded-full ${config.bg} ${config.text} font-medium`}>
|
|
79
|
+
{affectedCount} affected
|
|
80
|
+
</span>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
{description && (
|
|
84
|
+
<p className={`text-sm mt-1 ${config.text} opacity-80`}>
|
|
85
|
+
{description}
|
|
86
|
+
</p>
|
|
87
|
+
)}
|
|
88
|
+
{actionLabel && onAction && (
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
onClick={onAction}
|
|
92
|
+
className={`inline-flex items-center gap-1 text-sm font-medium mt-2 ${config.text} hover:underline`}
|
|
93
|
+
>
|
|
94
|
+
{actionLabel}
|
|
95
|
+
<ArrowRight className="h-3 w-3" />
|
|
96
|
+
</button>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* Dismiss */}
|
|
101
|
+
{onDismiss && (
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
onClick={onDismiss}
|
|
105
|
+
className={`flex-shrink-0 p-1 rounded hover:bg-white/50 transition-colors ${config.text} opacity-60 hover:opacity-100`}
|
|
106
|
+
aria-label="Dismiss"
|
|
107
|
+
>
|
|
108
|
+
<X className="h-4 w-4" />
|
|
109
|
+
</button>
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Clock, AlertTriangle, User } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Types & Interfaces
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
export interface CaseQueueItemProps {
|
|
8
|
+
/** Case ID or number */
|
|
9
|
+
caseNumber: string;
|
|
10
|
+
/** Case subject/title */
|
|
11
|
+
subject: string;
|
|
12
|
+
/** Priority level */
|
|
13
|
+
priority: 'critical' | 'high' | 'medium' | 'low';
|
|
14
|
+
/** Current status */
|
|
15
|
+
status: string;
|
|
16
|
+
/** Status color variant */
|
|
17
|
+
statusColor?: 'slate' | 'blue' | 'indigo' | 'purple' | 'green' | 'red' | 'yellow' | 'orange' | 'teal';
|
|
18
|
+
/** Assignee name */
|
|
19
|
+
assignee?: string;
|
|
20
|
+
/** Account/customer name */
|
|
21
|
+
account?: string;
|
|
22
|
+
/** SLA deadline (ISO timestamp) */
|
|
23
|
+
/** Whether the SLA is breached */
|
|
24
|
+
slaBreach?: boolean;
|
|
25
|
+
/** Time remaining display string (e.g., "2h 15m") */
|
|
26
|
+
slaTimeRemaining?: string;
|
|
27
|
+
/** Click handler */
|
|
28
|
+
onClick?: () => void;
|
|
29
|
+
/** Whether the item is selected */
|
|
30
|
+
selected?: boolean;
|
|
31
|
+
/** Additional className */
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Helpers
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
const priorityConfig: Record<string, { bg: string; text: string; label: string; dot: string }> = {
|
|
40
|
+
critical: { bg: 'bg-red-50', text: 'text-red-700', label: 'Critical', dot: 'bg-red-500' },
|
|
41
|
+
high: { bg: 'bg-orange-50', text: 'text-orange-700', label: 'High', dot: 'bg-orange-500' },
|
|
42
|
+
medium: { bg: 'bg-yellow-50', text: 'text-yellow-700', label: 'Medium', dot: 'bg-yellow-500' },
|
|
43
|
+
low: { bg: 'bg-blue-50', text: 'text-blue-700', label: 'Low', dot: 'bg-blue-500' },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const statusColors: Record<string, { bg: string; text: string }> = {
|
|
47
|
+
slate: { bg: 'bg-slate-100', text: 'text-slate-700' },
|
|
48
|
+
blue: { bg: 'bg-blue-100', text: 'text-blue-700' },
|
|
49
|
+
indigo: { bg: 'bg-indigo-100', text: 'text-indigo-700' },
|
|
50
|
+
purple: { bg: 'bg-purple-100', text: 'text-purple-700' },
|
|
51
|
+
green: { bg: 'bg-green-100', text: 'text-green-700' },
|
|
52
|
+
red: { bg: 'bg-red-100', text: 'text-red-700' },
|
|
53
|
+
yellow: { bg: 'bg-yellow-100', text: 'text-yellow-700' },
|
|
54
|
+
orange: { bg: 'bg-orange-100', text: 'text-orange-700' },
|
|
55
|
+
teal: { bg: 'bg-teal-100', text: 'text-teal-700' },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Component
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* CaseQueueItem — Priority-sorted case card for support queue views
|
|
64
|
+
*
|
|
65
|
+
* Shows case subject, priority indicator, status, assignee, and SLA countdown.
|
|
66
|
+
* Designed for use in the Support Center's case queue list.
|
|
67
|
+
*/
|
|
68
|
+
export default function CaseQueueItem({
|
|
69
|
+
caseNumber,
|
|
70
|
+
subject,
|
|
71
|
+
priority,
|
|
72
|
+
status,
|
|
73
|
+
statusColor = 'slate',
|
|
74
|
+
assignee,
|
|
75
|
+
account,
|
|
76
|
+
slaBreach = false,
|
|
77
|
+
slaTimeRemaining,
|
|
78
|
+
onClick,
|
|
79
|
+
selected = false,
|
|
80
|
+
className = '',
|
|
81
|
+
}: CaseQueueItemProps) {
|
|
82
|
+
const prio = priorityConfig[priority] || priorityConfig.medium;
|
|
83
|
+
const stColor = statusColors[statusColor];
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
onClick={onClick}
|
|
88
|
+
role={onClick ? 'button' : undefined}
|
|
89
|
+
tabIndex={onClick ? 0 : undefined}
|
|
90
|
+
onKeyDown={onClick ? (e) => { if (e.key === 'Enter' || e.key === ' ') onClick(); } : undefined}
|
|
91
|
+
className={`
|
|
92
|
+
group relative rounded-lg border bg-white dark:bg-ink-900 p-4
|
|
93
|
+
${selected ? 'border-primary-400 ring-2 ring-primary-100' : 'border-paper-200 dark:border-ink-700'}
|
|
94
|
+
${onClick ? 'cursor-pointer hover:shadow-md hover:border-primary-300 transition-all' : ''}
|
|
95
|
+
${slaBreach ? 'border-l-4 border-l-red-500' : ''}
|
|
96
|
+
${className}
|
|
97
|
+
`}
|
|
98
|
+
aria-label={`Case ${caseNumber}: ${subject}`}
|
|
99
|
+
>
|
|
100
|
+
{/* Top row: case number + priority + SLA */}
|
|
101
|
+
<div className="flex items-center justify-between gap-2 mb-2">
|
|
102
|
+
<div className="flex items-center gap-2">
|
|
103
|
+
<span className="text-xs font-mono text-ink-400">{caseNumber}</span>
|
|
104
|
+
<span className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium ${prio.bg} ${prio.text}`}>
|
|
105
|
+
<span className={`w-1.5 h-1.5 rounded-full ${prio.dot}`} />
|
|
106
|
+
{prio.label}
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
{/* SLA countdown */}
|
|
111
|
+
{(slaTimeRemaining || slaBreach) && (
|
|
112
|
+
<div className={`flex items-center gap-1 text-xs ${slaBreach ? 'text-red-600 font-medium' : 'text-ink-500'}`}>
|
|
113
|
+
{slaBreach ? <AlertTriangle className="h-3 w-3" /> : <Clock className="h-3 w-3" />}
|
|
114
|
+
<span>{slaBreach ? 'SLA Breached' : slaTimeRemaining}</span>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{/* Subject */}
|
|
120
|
+
<h4 className="text-sm font-medium text-ink-900 dark:text-ink-100 mb-1 line-clamp-2">
|
|
121
|
+
{subject}
|
|
122
|
+
</h4>
|
|
123
|
+
|
|
124
|
+
{/* Account */}
|
|
125
|
+
{account && (
|
|
126
|
+
<p className="text-xs text-ink-500 mb-2">{account}</p>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{/* Bottom row: status + assignee */}
|
|
130
|
+
<div className="flex items-center justify-between gap-2">
|
|
131
|
+
{stColor && (
|
|
132
|
+
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${stColor.bg} ${stColor.text}`}>
|
|
133
|
+
{status}
|
|
134
|
+
</span>
|
|
135
|
+
)}
|
|
136
|
+
{assignee && (
|
|
137
|
+
<div className="flex items-center gap-1 text-xs text-ink-500">
|
|
138
|
+
<User className="h-3 w-3" />
|
|
139
|
+
<span>{assignee}</span>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Types & Interfaces
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
export interface ConfidenceBadgeProps {
|
|
6
|
+
/** Confidence score (0-100) */
|
|
7
|
+
score: number;
|
|
8
|
+
/** Whether to show the score as text */
|
|
9
|
+
showScore?: boolean;
|
|
10
|
+
/** Size variant */
|
|
11
|
+
size?: 'sm' | 'md';
|
|
12
|
+
/** Additional className */
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Component
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* ConfidenceBadge — Compact inline confidence display
|
|
22
|
+
*
|
|
23
|
+
* Shows a color-coded badge with optional score text.
|
|
24
|
+
* Used in table cells, Kanban cards, and list items where
|
|
25
|
+
* the full ConfidenceIndicator gauge would be too large.
|
|
26
|
+
*/
|
|
27
|
+
export default function ConfidenceBadge({
|
|
28
|
+
score,
|
|
29
|
+
showScore = true,
|
|
30
|
+
size = 'md',
|
|
31
|
+
className = '',
|
|
32
|
+
}: ConfidenceBadgeProps) {
|
|
33
|
+
const normalizedScore = Math.min(100, Math.max(0, score));
|
|
34
|
+
|
|
35
|
+
const colorClasses = normalizedScore >= 80
|
|
36
|
+
? 'bg-success-50 text-success-700 border-success-200'
|
|
37
|
+
: normalizedScore >= 50
|
|
38
|
+
? 'bg-warning-50 text-warning-700 border-warning-200'
|
|
39
|
+
: 'bg-error-50 text-error-700 border-error-200';
|
|
40
|
+
|
|
41
|
+
const dotColor = normalizedScore >= 80
|
|
42
|
+
? 'bg-success-500'
|
|
43
|
+
: normalizedScore >= 50
|
|
44
|
+
? 'bg-warning-500'
|
|
45
|
+
: 'bg-error-500';
|
|
46
|
+
|
|
47
|
+
const sizeClasses = {
|
|
48
|
+
sm: { wrapper: 'px-1.5 py-0.5 text-[10px]', dot: 'w-1.5 h-1.5' },
|
|
49
|
+
md: { wrapper: 'px-2 py-0.5 text-xs', dot: 'w-2 h-2' },
|
|
50
|
+
};
|
|
51
|
+
const sizes = sizeClasses[size];
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<span
|
|
55
|
+
className={`inline-flex items-center gap-1 rounded-full border font-medium ${sizes.wrapper} ${colorClasses} ${className}`}
|
|
56
|
+
title={`${normalizedScore}% confidence`}
|
|
57
|
+
>
|
|
58
|
+
<span className={`${sizes.dot} rounded-full ${dotColor} flex-shrink-0`} />
|
|
59
|
+
{showScore && <span>{normalizedScore}%</span>}
|
|
60
|
+
</span>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Types & Interfaces
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
export interface ConfidenceIndicatorProps {
|
|
6
|
+
/** Confidence score (0-100) */
|
|
7
|
+
score: number;
|
|
8
|
+
/** Label to display below the gauge */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Size in pixels */
|
|
11
|
+
size?: number;
|
|
12
|
+
/** Stroke width */
|
|
13
|
+
strokeWidth?: number;
|
|
14
|
+
/** Additional className */
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Component
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* ConfidenceIndicator — Circular gauge showing confidence percentage
|
|
24
|
+
*
|
|
25
|
+
* Renders a circular progress arc with color based on score thresholds:
|
|
26
|
+
* - >= 80: green (high confidence)
|
|
27
|
+
* - >= 50: yellow (medium confidence)
|
|
28
|
+
* - < 50: red (low confidence)
|
|
29
|
+
*
|
|
30
|
+
* Used in review workflows and process views to show system confidence
|
|
31
|
+
* in automated actions.
|
|
32
|
+
*/
|
|
33
|
+
export default function ConfidenceIndicator({
|
|
34
|
+
score,
|
|
35
|
+
label,
|
|
36
|
+
size = 80,
|
|
37
|
+
strokeWidth = 6,
|
|
38
|
+
className = '',
|
|
39
|
+
}: ConfidenceIndicatorProps) {
|
|
40
|
+
const normalizedScore = Math.min(100, Math.max(0, score));
|
|
41
|
+
const radius = (size - strokeWidth) / 2;
|
|
42
|
+
const circumference = 2 * Math.PI * radius;
|
|
43
|
+
const offset = circumference - (normalizedScore / 100) * circumference;
|
|
44
|
+
const center = size / 2;
|
|
45
|
+
|
|
46
|
+
const color = normalizedScore >= 80 ? '#10b981' : // success green
|
|
47
|
+
normalizedScore >= 50 ? '#f59e0b' : // warning amber
|
|
48
|
+
'#ef4444'; // error red
|
|
49
|
+
|
|
50
|
+
const bgColor = normalizedScore >= 80 ? '#d1fae5' :
|
|
51
|
+
normalizedScore >= 50 ? '#fef3c7' :
|
|
52
|
+
'#fee2e2';
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className={`inline-flex flex-col items-center ${className}`}>
|
|
56
|
+
<div className="relative" style={{ width: size, height: size }}>
|
|
57
|
+
<svg width={size} height={size} className="transform -rotate-90">
|
|
58
|
+
{/* Background circle */}
|
|
59
|
+
<circle
|
|
60
|
+
cx={center}
|
|
61
|
+
cy={center}
|
|
62
|
+
r={radius}
|
|
63
|
+
fill="none"
|
|
64
|
+
stroke={bgColor}
|
|
65
|
+
strokeWidth={strokeWidth}
|
|
66
|
+
/>
|
|
67
|
+
{/* Progress arc */}
|
|
68
|
+
<circle
|
|
69
|
+
cx={center}
|
|
70
|
+
cy={center}
|
|
71
|
+
r={radius}
|
|
72
|
+
fill="none"
|
|
73
|
+
stroke={color}
|
|
74
|
+
strokeWidth={strokeWidth}
|
|
75
|
+
strokeLinecap="round"
|
|
76
|
+
strokeDasharray={circumference}
|
|
77
|
+
strokeDashoffset={offset}
|
|
78
|
+
className="transition-all duration-500 ease-out"
|
|
79
|
+
/>
|
|
80
|
+
</svg>
|
|
81
|
+
{/* Center text */}
|
|
82
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
83
|
+
<span
|
|
84
|
+
className="font-bold text-ink-900 dark:text-ink-100"
|
|
85
|
+
style={{ fontSize: size * 0.22 }}
|
|
86
|
+
>
|
|
87
|
+
{normalizedScore}%
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
{label && (
|
|
92
|
+
<span className="text-xs text-ink-500 mt-1">{label}</span>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|