@wealthx/shadcn 1.5.14 → 1.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +80 -80
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-AKWN5ZQG.mjs → chunk-A43XIVO6.mjs} +57 -29
- package/dist/{chunk-ZA44WICP.mjs → chunk-FGHM34AV.mjs} +38 -31
- package/dist/{chunk-EB626HVW.mjs → chunk-LRQSY3TP.mjs} +33 -0
- package/dist/{chunk-J7KQON2N.mjs → chunk-RKHKBEE6.mjs} +1 -1
- package/dist/components/ui/ai-conversations.js +32 -0
- package/dist/components/ui/ai-conversations.mjs +1 -1
- package/dist/components/ui/appointment-book-dialog.js +1 -1
- package/dist/components/ui/appointment-book-dialog.mjs +1 -1
- package/dist/components/ui/kanban-column.js +123 -43
- package/dist/components/ui/kanban-column.mjs +2 -1
- package/dist/components/ui/pipeline-board.js +173 -86
- package/dist/components/ui/pipeline-board.mjs +3 -2
- package/dist/index.js +120 -59
- package/dist/index.mjs +4 -4
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/ui/ai-conversations.tsx +45 -0
- package/src/components/ui/appointment-book-dialog.tsx +1 -1
- package/src/components/ui/kanban-column.tsx +78 -37
- package/src/components/ui/pipeline-board.tsx +73 -49
- package/src/styles/styles-css.ts +1 -1
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
MapPin,
|
|
20
20
|
MessageSquare,
|
|
21
21
|
MoreHorizontal,
|
|
22
|
+
Navigation,
|
|
22
23
|
Paperclip,
|
|
23
24
|
Phone,
|
|
24
25
|
PhoneCall,
|
|
@@ -154,6 +155,8 @@ export interface AiConvDataField {
|
|
|
154
155
|
export interface AiConvAppointmentData {
|
|
155
156
|
datetime: string;
|
|
156
157
|
meetingType: AiConvMeetingType;
|
|
158
|
+
/** Meeting link (video), phone number (phone), or address (in-person). */
|
|
159
|
+
meetingDetail?: string;
|
|
157
160
|
status: "requested" | "confirmed" | "pending" | "cancelled";
|
|
158
161
|
}
|
|
159
162
|
|
|
@@ -1298,6 +1301,12 @@ const MEETING_LABEL: Record<AiConvMeetingType, string> = {
|
|
|
1298
1301
|
"in-person": "In Person",
|
|
1299
1302
|
};
|
|
1300
1303
|
|
|
1304
|
+
const MEETING_DETAIL_ICON: Record<AiConvMeetingType, React.ElementType> = {
|
|
1305
|
+
video: Link2,
|
|
1306
|
+
phone: PhoneCall,
|
|
1307
|
+
"in-person": Navigation,
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1301
1310
|
interface AppointmentSectionProps {
|
|
1302
1311
|
appointment: AiConvAppointmentData;
|
|
1303
1312
|
contactId: string;
|
|
@@ -1307,6 +1316,36 @@ interface AppointmentSectionProps {
|
|
|
1307
1316
|
onRescheduleAppointment?: (contactId: string) => void;
|
|
1308
1317
|
}
|
|
1309
1318
|
|
|
1319
|
+
function MeetingDetailRow({
|
|
1320
|
+
meetingType,
|
|
1321
|
+
detail,
|
|
1322
|
+
}: {
|
|
1323
|
+
meetingType: AiConvMeetingType;
|
|
1324
|
+
detail: string;
|
|
1325
|
+
}) {
|
|
1326
|
+
const DetailIcon = MEETING_DETAIL_ICON[meetingType];
|
|
1327
|
+
const isLink = detail.startsWith("http");
|
|
1328
|
+
return (
|
|
1329
|
+
<div className="flex items-center gap-2">
|
|
1330
|
+
<DetailIcon className="size-4 shrink-0 text-muted-foreground" />
|
|
1331
|
+
{isLink ? (
|
|
1332
|
+
<a
|
|
1333
|
+
href={detail}
|
|
1334
|
+
target="_blank"
|
|
1335
|
+
rel="noopener noreferrer"
|
|
1336
|
+
className="text-sm text-primary underline underline-offset-2 break-all hover:text-primary/80"
|
|
1337
|
+
>
|
|
1338
|
+
{detail}
|
|
1339
|
+
</a>
|
|
1340
|
+
) : (
|
|
1341
|
+
<span className="text-sm text-muted-foreground break-all">
|
|
1342
|
+
{detail}
|
|
1343
|
+
</span>
|
|
1344
|
+
)}
|
|
1345
|
+
</div>
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1310
1349
|
function AppointmentSection({
|
|
1311
1350
|
appointment,
|
|
1312
1351
|
contactId,
|
|
@@ -1332,6 +1371,12 @@ function AppointmentSection({
|
|
|
1332
1371
|
{MEETING_LABEL[appointment.meetingType]}
|
|
1333
1372
|
</span>
|
|
1334
1373
|
</div>
|
|
1374
|
+
{appointment.meetingDetail && (
|
|
1375
|
+
<MeetingDetailRow
|
|
1376
|
+
meetingType={appointment.meetingType}
|
|
1377
|
+
detail={appointment.meetingDetail}
|
|
1378
|
+
/>
|
|
1379
|
+
)}
|
|
1335
1380
|
<span
|
|
1336
1381
|
className={cn("text-sm font-medium", {
|
|
1337
1382
|
"text-warning-text": appointment.status === "requested",
|
|
@@ -612,7 +612,7 @@ export function AppointmentBookDialog({
|
|
|
612
612
|
|
|
613
613
|
<Separator />
|
|
614
614
|
|
|
615
|
-
<div className="flex flex-col gap-4">
|
|
615
|
+
<div className="flex flex-col gap-4 overflow-y-auto max-h-[calc(90vh-200px)]">
|
|
616
616
|
{!isClientMode && (
|
|
617
617
|
<div className="flex flex-col gap-1.5">
|
|
618
618
|
<Label>Client</Label>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { MoreVertical } from "lucide-react";
|
|
2
|
+
import { MoreVertical, Plus } from "lucide-react";
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
4
|
import { useThemeVars } from "@/lib/theme-provider";
|
|
5
5
|
import { formatCurrency } from "@/lib/format-currency";
|
|
@@ -15,6 +15,12 @@ import {
|
|
|
15
15
|
} from "@/components/ui/dropdown-menu";
|
|
16
16
|
import { LeadCard, OpportunityCard } from "@/components/ui/opportunity-card";
|
|
17
17
|
import type { OpportunityCardProps } from "@/components/ui/opportunity-card";
|
|
18
|
+
import {
|
|
19
|
+
Tooltip,
|
|
20
|
+
TooltipContent,
|
|
21
|
+
TooltipProvider,
|
|
22
|
+
TooltipTrigger,
|
|
23
|
+
} from "@/components/ui/tooltip";
|
|
18
24
|
|
|
19
25
|
/**
|
|
20
26
|
* KanbanColumn — WealthX DS (L4 Section)
|
|
@@ -52,6 +58,12 @@ export interface KanbanColumnStage {
|
|
|
52
58
|
* Defaults to `"var(--color-border)"`.
|
|
53
59
|
*/
|
|
54
60
|
accentColor?: string | null;
|
|
61
|
+
/**
|
|
62
|
+
* Optional color for the stage count number in the column header.
|
|
63
|
+
* Pass a CSS variable string: `"var(--color-destructive)"`.
|
|
64
|
+
* Defaults to `text-muted-foreground`.
|
|
65
|
+
*/
|
|
66
|
+
countColor?: string | null;
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
export interface KanbanColumnProps {
|
|
@@ -91,6 +103,11 @@ export interface KanbanColumnProps {
|
|
|
91
103
|
onEditColumn?: () => void;
|
|
92
104
|
/** Only shown for non-default columns. */
|
|
93
105
|
onDeleteColumn?: () => void;
|
|
106
|
+
/**
|
|
107
|
+
* When provided, renders a `+` icon button in the column header.
|
|
108
|
+
* Intended for the Leads column — opens the Add Lead flow.
|
|
109
|
+
*/
|
|
110
|
+
onAddLead?: () => void;
|
|
94
111
|
|
|
95
112
|
// ── Card callbacks (forwarded to each OpportunityCard) ───────
|
|
96
113
|
onTaskToggle?: (opportunityId: string, taskId: string) => void;
|
|
@@ -115,10 +132,6 @@ export interface KanbanColumnProps {
|
|
|
115
132
|
// Helpers
|
|
116
133
|
// ---------------------------------------------------------------------------
|
|
117
134
|
|
|
118
|
-
function formatTotalValue(value: number): string {
|
|
119
|
-
return formatCurrency(value);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
135
|
function growthColor(growth: number): string {
|
|
123
136
|
// Use darkened text tokens — full-saturation tokens (#4CAF50, #F44336) fail 4.5:1 on white bg
|
|
124
137
|
return growth > 0
|
|
@@ -142,6 +155,7 @@ export function KanbanColumn({
|
|
|
142
155
|
loaderRef,
|
|
143
156
|
onEditColumn,
|
|
144
157
|
onDeleteColumn,
|
|
158
|
+
onAddLead,
|
|
145
159
|
onTaskToggle,
|
|
146
160
|
onMarkAsDone,
|
|
147
161
|
onMoveToNextStage,
|
|
@@ -203,41 +217,68 @@ export function KanbanColumn({
|
|
|
203
217
|
{/* Title row */}
|
|
204
218
|
<div className="flex items-center justify-between gap-2">
|
|
205
219
|
<h2 className="text-sm font-semibold text-foreground">
|
|
206
|
-
<span
|
|
220
|
+
<span
|
|
221
|
+
className={cn(!stage.countColor && "text-muted-foreground")}
|
|
222
|
+
style={stage.countColor ? { color: stage.countColor } : undefined}
|
|
223
|
+
>
|
|
224
|
+
{stage.count}
|
|
225
|
+
</span>{" "}
|
|
207
226
|
{stage.name}
|
|
208
227
|
</h2>
|
|
209
228
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
{onEditColumn && (
|
|
223
|
-
<DropdownMenuItem onClick={onEditColumn}>
|
|
224
|
-
Edit column settings
|
|
225
|
-
</DropdownMenuItem>
|
|
226
|
-
)}
|
|
227
|
-
{!isDefault && onDeleteColumn && (
|
|
228
|
-
<>
|
|
229
|
-
{onEditColumn && <DropdownMenuSeparator />}
|
|
230
|
-
<DropdownMenuItem
|
|
231
|
-
onClick={onDeleteColumn}
|
|
232
|
-
className="text-destructive focus:text-destructive"
|
|
229
|
+
<div className="flex items-center gap-0.5">
|
|
230
|
+
{onAddLead && (
|
|
231
|
+
<TooltipProvider>
|
|
232
|
+
<Tooltip>
|
|
233
|
+
<TooltipTrigger asChild>
|
|
234
|
+
<button
|
|
235
|
+
className={cn(
|
|
236
|
+
buttonVariants({ variant: "ghost", size: "icon" }),
|
|
237
|
+
"size-7 shrink-0",
|
|
238
|
+
)}
|
|
239
|
+
onClick={onAddLead}
|
|
240
|
+
aria-label="Add lead"
|
|
233
241
|
>
|
|
234
|
-
|
|
242
|
+
<Plus className="size-4" />
|
|
243
|
+
</button>
|
|
244
|
+
</TooltipTrigger>
|
|
245
|
+
<TooltipContent>Add lead</TooltipContent>
|
|
246
|
+
</Tooltip>
|
|
247
|
+
</TooltipProvider>
|
|
248
|
+
)}
|
|
249
|
+
|
|
250
|
+
{hasMenu && (
|
|
251
|
+
<DropdownMenu>
|
|
252
|
+
<DropdownMenuTrigger
|
|
253
|
+
className={cn(
|
|
254
|
+
buttonVariants({ variant: "ghost", size: "icon" }),
|
|
255
|
+
"-mr-1 size-7 shrink-0",
|
|
256
|
+
)}
|
|
257
|
+
aria-label="Column actions"
|
|
258
|
+
>
|
|
259
|
+
<MoreVertical className="size-4" />
|
|
260
|
+
</DropdownMenuTrigger>
|
|
261
|
+
<DropdownMenuContent align="end">
|
|
262
|
+
{onEditColumn && (
|
|
263
|
+
<DropdownMenuItem onClick={onEditColumn}>
|
|
264
|
+
Edit column settings
|
|
235
265
|
</DropdownMenuItem>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
266
|
+
)}
|
|
267
|
+
{!isDefault && onDeleteColumn && (
|
|
268
|
+
<>
|
|
269
|
+
{onEditColumn && <DropdownMenuSeparator />}
|
|
270
|
+
<DropdownMenuItem
|
|
271
|
+
onClick={onDeleteColumn}
|
|
272
|
+
className="text-destructive focus:text-destructive"
|
|
273
|
+
>
|
|
274
|
+
Delete column
|
|
275
|
+
</DropdownMenuItem>
|
|
276
|
+
</>
|
|
277
|
+
)}
|
|
278
|
+
</DropdownMenuContent>
|
|
279
|
+
</DropdownMenu>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
241
282
|
</div>
|
|
242
283
|
|
|
243
284
|
{/* Stats row */}
|
|
@@ -253,8 +294,8 @@ export function KanbanColumn({
|
|
|
253
294
|
) : (
|
|
254
295
|
<span />
|
|
255
296
|
)}
|
|
256
|
-
<span className="text-
|
|
257
|
-
{
|
|
297
|
+
<span className="text-sm font-bold tabular-nums text-foreground">
|
|
298
|
+
{formatCurrency(stage.totalValue)}
|
|
258
299
|
</span>
|
|
259
300
|
</div>
|
|
260
301
|
</div>
|
|
@@ -5,10 +5,7 @@ import { cn } from "@/lib/utils";
|
|
|
5
5
|
import { Input } from "@/components/ui/input";
|
|
6
6
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
|
7
7
|
import { KanbanColumn } from "@/components/ui/kanban-column";
|
|
8
|
-
import type {
|
|
9
|
-
KanbanColumnProps,
|
|
10
|
-
KanbanColumnStage,
|
|
11
|
-
} from "@/components/ui/kanban-column";
|
|
8
|
+
import type { KanbanColumnStage } from "@/components/ui/kanban-column";
|
|
12
9
|
import type { OpportunityCardProps } from "@/components/ui/opportunity-card";
|
|
13
10
|
|
|
14
11
|
/**
|
|
@@ -53,10 +50,21 @@ export interface PipelineBoardColumn {
|
|
|
53
50
|
isDropTarget?: boolean;
|
|
54
51
|
/** Whether this is a fixed/system column (no Delete in menu). */
|
|
55
52
|
isDefault?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* When true, this column is rendered in a fixed left section and does not
|
|
55
|
+
* scroll horizontally with the rest of the board. Use for the High Priority
|
|
56
|
+
* column, which must always be visible.
|
|
57
|
+
*/
|
|
58
|
+
isPinned?: boolean;
|
|
56
59
|
isLoading?: boolean;
|
|
57
60
|
isLoadingMore?: boolean;
|
|
58
61
|
hasMore?: boolean;
|
|
59
62
|
loaderRef?: React.Ref<HTMLDivElement>;
|
|
63
|
+
/**
|
|
64
|
+
* When provided, renders a `+` icon button in the column header.
|
|
65
|
+
* Intended for the Leads column — opens the Add Lead flow.
|
|
66
|
+
*/
|
|
67
|
+
onAddLead?: () => void;
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
export interface PipelineBoardProps {
|
|
@@ -78,8 +86,6 @@ export interface PipelineBoardProps {
|
|
|
78
86
|
// ── Column callbacks ─────────────────────────────────────────
|
|
79
87
|
onEditColumn?: (stageId: string) => void;
|
|
80
88
|
onDeleteColumn?: (stageId: string) => void;
|
|
81
|
-
|
|
82
|
-
// ── Toolbar ──────────────────────────────────────────────────
|
|
83
89
|
onRefresh?: () => void;
|
|
84
90
|
|
|
85
91
|
// ── Card callbacks ───────────────────────────────────────────
|
|
@@ -217,6 +223,48 @@ export function PipelineBoard({
|
|
|
217
223
|
const hasToolbar =
|
|
218
224
|
onSearchChange || (filterOptions.length > 0 && onFilterChange);
|
|
219
225
|
|
|
226
|
+
const pinnedCols = columns.filter((c) => c.isPinned);
|
|
227
|
+
const scrollableCols = columns.filter((c) => !c.isPinned);
|
|
228
|
+
|
|
229
|
+
const renderColumn = (col: PipelineBoardColumn) => (
|
|
230
|
+
<KanbanColumn
|
|
231
|
+
key={col.key}
|
|
232
|
+
stage={col.stage}
|
|
233
|
+
opportunities={col.opportunities}
|
|
234
|
+
isDragging={col.isDragging}
|
|
235
|
+
isDropTarget={col.isDropTarget}
|
|
236
|
+
isDefault={col.isDefault}
|
|
237
|
+
isLoading={col.isLoading}
|
|
238
|
+
isLoadingMore={col.isLoadingMore}
|
|
239
|
+
hasMore={col.hasMore}
|
|
240
|
+
loaderRef={col.loaderRef}
|
|
241
|
+
onEditColumn={
|
|
242
|
+
!col.isPinned && onEditColumn
|
|
243
|
+
? () => onEditColumn(col.stage.id)
|
|
244
|
+
: undefined
|
|
245
|
+
}
|
|
246
|
+
onDeleteColumn={
|
|
247
|
+
onDeleteColumn && !col.isDefault && !col.isPinned
|
|
248
|
+
? () => onDeleteColumn(col.stage.id)
|
|
249
|
+
: undefined
|
|
250
|
+
}
|
|
251
|
+
onCardDrop={
|
|
252
|
+
onMoveCard ? (cardId) => onMoveCard(cardId, col.key) : undefined
|
|
253
|
+
}
|
|
254
|
+
onCardClick={onCardClick}
|
|
255
|
+
onTaskToggle={onTaskToggle}
|
|
256
|
+
onMarkAsDone={onMarkAsDone}
|
|
257
|
+
onMoveToNextStage={onMoveToNextStage}
|
|
258
|
+
onSendLoanApplication={col.onSendLoanApplication}
|
|
259
|
+
onViewDetails={onViewDetails}
|
|
260
|
+
onChangePriority={onChangePriority}
|
|
261
|
+
onPutOnHold={onPutOnHold}
|
|
262
|
+
onDeleteOpportunity={onDeleteOpportunity}
|
|
263
|
+
onAddLead={col.onAddLead}
|
|
264
|
+
submittingOpportunityId={submittingOpportunityId}
|
|
265
|
+
/>
|
|
266
|
+
);
|
|
267
|
+
|
|
220
268
|
return (
|
|
221
269
|
<div
|
|
222
270
|
className={cn("flex h-full flex-col bg-muted/20", className)}
|
|
@@ -234,51 +282,27 @@ export function PipelineBoard({
|
|
|
234
282
|
/>
|
|
235
283
|
)}
|
|
236
284
|
|
|
237
|
-
{/* ── Board
|
|
238
|
-
<div className="flex flex-1
|
|
239
|
-
{columns
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
opportunities={col.opportunities}
|
|
244
|
-
isDragging={col.isDragging}
|
|
245
|
-
isDropTarget={col.isDropTarget}
|
|
246
|
-
isDefault={col.isDefault}
|
|
247
|
-
isLoading={col.isLoading}
|
|
248
|
-
isLoadingMore={col.isLoadingMore}
|
|
249
|
-
hasMore={col.hasMore}
|
|
250
|
-
loaderRef={col.loaderRef}
|
|
251
|
-
onEditColumn={
|
|
252
|
-
onEditColumn ? () => onEditColumn(col.stage.id) : undefined
|
|
253
|
-
}
|
|
254
|
-
onDeleteColumn={
|
|
255
|
-
onDeleteColumn && !col.isDefault
|
|
256
|
-
? () => onDeleteColumn(col.stage.id)
|
|
257
|
-
: undefined
|
|
258
|
-
}
|
|
259
|
-
onCardDrop={
|
|
260
|
-
onMoveCard ? (cardId) => onMoveCard(cardId, col.key) : undefined
|
|
261
|
-
}
|
|
262
|
-
onCardClick={onCardClick}
|
|
263
|
-
onTaskToggle={onTaskToggle}
|
|
264
|
-
onMarkAsDone={onMarkAsDone}
|
|
265
|
-
onMoveToNextStage={onMoveToNextStage}
|
|
266
|
-
onSendLoanApplication={col.onSendLoanApplication}
|
|
267
|
-
onViewDetails={onViewDetails}
|
|
268
|
-
onChangePriority={onChangePriority}
|
|
269
|
-
onPutOnHold={onPutOnHold}
|
|
270
|
-
onDeleteOpportunity={onDeleteOpportunity}
|
|
271
|
-
submittingOpportunityId={submittingOpportunityId}
|
|
272
|
-
/>
|
|
273
|
-
))}
|
|
274
|
-
|
|
275
|
-
{columns.length === 0 && (
|
|
276
|
-
<div className="flex flex-1 items-center justify-center">
|
|
277
|
-
<p className="text-sm text-muted-foreground">
|
|
278
|
-
No columns to display.
|
|
279
|
-
</p>
|
|
285
|
+
{/* ── Board area ── */}
|
|
286
|
+
<div className="flex flex-1 overflow-hidden">
|
|
287
|
+
{/* Pinned columns — always visible, do not scroll */}
|
|
288
|
+
{pinnedCols.length > 0 && (
|
|
289
|
+
<div className="flex shrink-0 gap-3 border-r border-border p-4">
|
|
290
|
+
{pinnedCols.map(renderColumn)}
|
|
280
291
|
</div>
|
|
281
292
|
)}
|
|
293
|
+
|
|
294
|
+
{/* Scrollable columns */}
|
|
295
|
+
<div className="flex flex-1 gap-3 overflow-x-auto p-4">
|
|
296
|
+
{scrollableCols.map(renderColumn)}
|
|
297
|
+
|
|
298
|
+
{columns.length === 0 && (
|
|
299
|
+
<div className="flex flex-1 items-center justify-center">
|
|
300
|
+
<p className="text-sm text-muted-foreground">
|
|
301
|
+
No columns to display.
|
|
302
|
+
</p>
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
282
306
|
</div>
|
|
283
307
|
</div>
|
|
284
308
|
);
|