realtimex-crm 0.13.1 → 0.13.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realtimex-crm",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "description": "RealTimeX CRM - A full-featured CRM built with React, shadcn-admin-kit, and Supabase. Fork of Atomic CRM with RealTimeX App SDK integration.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,6 +14,7 @@ import { SaleName } from "../sales/SaleName";
14
14
  import type { Company } from "../types";
15
15
  import { sizes } from "./sizes";
16
16
  import { CompanyMergeButton } from "./CompanyMergeButton";
17
+ import { CompanyHealthCard } from "./CompanyHealthCard";
17
18
 
18
19
  interface CompanyAsideProps {
19
20
  link?: string;
@@ -33,6 +34,8 @@ export const CompanyAside = ({ link = "edit" }: CompanyAsideProps) => {
33
34
  )}
34
35
  </div>
35
36
 
37
+ <CompanyHealthCard />
38
+
36
39
  <CompanyInfo record={record} />
37
40
 
38
41
  <AddressInfo record={record} />
@@ -168,7 +171,7 @@ const AdditionalInfo = ({ record }: { record: Company }) => {
168
171
  {record.description && (
169
172
  <p className="text-sm mb-1">{record.description}</p>
170
173
  )}
171
- {record.context_links && (
174
+ {record.context_links && Array.isArray(record.context_links) && (
172
175
  <div className="flex flex-col">
173
176
  {record.context_links.map((link, index) =>
174
177
  link ? (
@@ -0,0 +1,157 @@
1
+ import { formatDistance } from "date-fns";
2
+ import { Activity, HeartPulse } from "lucide-react";
3
+ import { useRecordContext } from "ra-core";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { Progress } from "@/components/ui/progress";
6
+
7
+ import { AsideSection } from "../misc/AsideSection";
8
+ import type { Company } from "../types";
9
+
10
+ export const CompanyHealthCard = () => {
11
+ const record = useRecordContext<Company>();
12
+ if (!record) return null;
13
+
14
+ const hasInternalHealth =
15
+ record.internal_heartbeat_score !== undefined ||
16
+ record.internal_heartbeat_status ||
17
+ record.days_since_last_activity !== undefined;
18
+
19
+ const hasExternalHealth =
20
+ record.external_heartbeat_status || record.external_heartbeat_checked_at;
21
+
22
+ if (!hasInternalHealth && !hasExternalHealth) {
23
+ return null;
24
+ }
25
+
26
+ return (
27
+ <AsideSection title="Company Health">
28
+ {/* Internal Heartbeat (Engagement) */}
29
+ {hasInternalHealth && (
30
+ <div className="mb-4">
31
+ <div className="flex items-center gap-2 mb-2">
32
+ <Activity className="w-4 h-4 text-muted-foreground" />
33
+ <span className="font-medium text-sm">Internal Engagement</span>
34
+ </div>
35
+
36
+ {record.internal_heartbeat_score !== undefined && (
37
+ <div className="mb-2">
38
+ <div className="flex items-center justify-between mb-1">
39
+ <span className="text-xs text-muted-foreground">
40
+ Engagement Score
41
+ </span>
42
+ <span className="text-xs font-medium">
43
+ {record.internal_heartbeat_score}/100
44
+ </span>
45
+ </div>
46
+ <Progress
47
+ value={record.internal_heartbeat_score}
48
+ className="h-2"
49
+ />
50
+ </div>
51
+ )}
52
+
53
+ {record.internal_heartbeat_status && (
54
+ <div className="mb-2">
55
+ <InternalStatusBadge status={record.internal_heartbeat_status} />
56
+ </div>
57
+ )}
58
+
59
+ {record.days_since_last_activity !== undefined && (
60
+ <div className="text-xs text-muted-foreground">
61
+ Last activity:{" "}
62
+ {record.days_since_last_activity === 0
63
+ ? "Today"
64
+ : record.days_since_last_activity === 1
65
+ ? "Yesterday"
66
+ : `${record.days_since_last_activity} days ago`}
67
+ </div>
68
+ )}
69
+
70
+ {record.internal_heartbeat_updated_at && (
71
+ <div className="text-xs text-muted-foreground mt-1">
72
+ Updated{" "}
73
+ {formatDistance(
74
+ new Date(record.internal_heartbeat_updated_at),
75
+ new Date(),
76
+ { addSuffix: true },
77
+ )}
78
+ </div>
79
+ )}
80
+ </div>
81
+ )}
82
+
83
+ {/* External Heartbeat (Entity Health) */}
84
+ {hasExternalHealth && (
85
+ <div>
86
+ <div className="flex items-center gap-2 mb-2">
87
+ <HeartPulse className="w-4 h-4 text-muted-foreground" />
88
+ <span className="font-medium text-sm">External Health</span>
89
+ </div>
90
+
91
+ {record.external_heartbeat_status && (
92
+ <div className="mb-2">
93
+ <ExternalStatusBadge status={record.external_heartbeat_status} />
94
+ </div>
95
+ )}
96
+
97
+ {record.external_heartbeat_checked_at && (
98
+ <div className="text-xs text-muted-foreground">
99
+ Last checked{" "}
100
+ {formatDistance(
101
+ new Date(record.external_heartbeat_checked_at),
102
+ new Date(),
103
+ { addSuffix: true },
104
+ )}
105
+ </div>
106
+ )}
107
+ </div>
108
+ )}
109
+ </AsideSection>
110
+ );
111
+ };
112
+
113
+ const InternalStatusBadge = ({ status }: { status: string }) => {
114
+ const statusConfig: Record<
115
+ string,
116
+ { variant: "default" | "secondary" | "outline" | "destructive"; label: string }
117
+ > = {
118
+ engaged: { variant: "default", label: "Engaged" },
119
+ quiet: { variant: "secondary", label: "Quiet" },
120
+ at_risk: { variant: "outline", label: "At Risk" },
121
+ unresponsive: { variant: "destructive", label: "Unresponsive" },
122
+ };
123
+
124
+ const config = statusConfig[status] || {
125
+ variant: "outline" as const,
126
+ label: status,
127
+ };
128
+
129
+ return (
130
+ <Badge variant={config.variant} className="text-xs">
131
+ {config.label}
132
+ </Badge>
133
+ );
134
+ };
135
+
136
+ const ExternalStatusBadge = ({ status }: { status: string }) => {
137
+ const statusConfig: Record<
138
+ string,
139
+ { variant: "default" | "secondary" | "outline" | "destructive"; label: string }
140
+ > = {
141
+ healthy: { variant: "default", label: "Healthy" },
142
+ risky: { variant: "outline", label: "Risky" },
143
+ dead: { variant: "destructive", label: "Dead" },
144
+ unknown: { variant: "secondary", label: "Unknown" },
145
+ };
146
+
147
+ const config = statusConfig[status] || {
148
+ variant: "outline" as const,
149
+ label: status,
150
+ };
151
+
152
+ return (
153
+ <Badge variant={config.variant} className="text-xs">
154
+ {config.label}
155
+ </Badge>
156
+ );
157
+ };