@wealthx/shadcn 1.5.0 → 1.5.2
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 +119 -119
- package/CHANGELOG.md +12 -0
- package/dist/chunk-G2EWIP2N.mjs +960 -0
- package/dist/{chunk-MHHA7QGO.mjs → chunk-ODO6BUOF.mjs} +1 -1
- package/dist/chunk-PX4M67XQ.mjs +301 -0
- package/dist/{chunk-FYUSF5KO.mjs → chunk-QRVEI6J3.mjs} +1 -1
- package/dist/{chunk-42NEC57Y.mjs → chunk-RAKBWNQH.mjs} +272 -3
- package/dist/components/ui/{contact-alert-dialog.js → contact-alert-dialog/index.js} +1029 -593
- package/dist/components/ui/contact-alert-dialog/index.mjs +31 -0
- package/dist/components/ui/file-preview-dialog.js +407 -100
- package/dist/components/ui/file-preview-dialog.mjs +3 -1
- package/dist/components/ui/kanban-column.js +408 -113
- package/dist/components/ui/kanban-column.mjs +3 -2
- package/dist/components/ui/opportunity-card.js +383 -88
- package/dist/components/ui/opportunity-card.mjs +2 -1
- package/dist/components/ui/pipeline-board.js +424 -129
- package/dist/components/ui/pipeline-board.mjs +4 -3
- package/dist/index.js +3081 -2282
- package/dist/index.mjs +39 -35
- package/dist/styles.css +1 -1
- package/package.json +6 -5
- package/src/components/index.tsx +3 -2
- package/src/components/ui/contact-alert-dialog/builder-ui.tsx +556 -0
- package/src/components/ui/contact-alert-dialog/config.ts +262 -0
- package/src/components/ui/contact-alert-dialog/contact-alert-dialog.tsx +214 -0
- package/src/components/ui/contact-alert-dialog/index.tsx +15 -0
- package/src/components/ui/contact-alert-dialog/types.ts +61 -0
- package/src/components/ui/contact-alert-dialog/utils.ts +93 -0
- package/src/components/ui/file-preview-dialog.tsx +299 -99
- package/src/components/ui/opportunity-card.tsx +328 -1
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +1 -1
- package/dist/chunk-5WMFKQZ6.mjs +0 -180
- package/dist/chunk-Y24TXIFJ.mjs +0 -518
- package/dist/components/ui/contact-alert-dialog.mjs +0 -27
- package/src/components/ui/contact-alert-dialog.tsx +0 -710
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
1
2
|
import {
|
|
2
3
|
Phone,
|
|
3
4
|
Mail,
|
|
4
5
|
User,
|
|
5
6
|
Users,
|
|
6
7
|
Calendar,
|
|
8
|
+
Check,
|
|
7
9
|
MoreVertical,
|
|
8
10
|
Clock,
|
|
9
11
|
ArrowRight,
|
|
10
12
|
Bot,
|
|
13
|
+
ChevronDown,
|
|
14
|
+
ChevronRight,
|
|
11
15
|
} from "lucide-react";
|
|
12
16
|
import { cn } from "@/lib/utils";
|
|
13
17
|
import { formatCurrency } from "@/lib/format-currency";
|
|
@@ -29,6 +33,7 @@ import {
|
|
|
29
33
|
AccordionTrigger,
|
|
30
34
|
} from "@/components/ui/accordion";
|
|
31
35
|
import { TaskCheckItem } from "@/components/ui/pipeline-primitives";
|
|
36
|
+
import { Progress } from "@/components/ui/progress";
|
|
32
37
|
import {
|
|
33
38
|
Tooltip,
|
|
34
39
|
TooltipContent,
|
|
@@ -127,6 +132,12 @@ export interface OpportunityCardProps {
|
|
|
127
132
|
draggable?: boolean;
|
|
128
133
|
onDragStart?: React.DragEventHandler<HTMLDivElement>;
|
|
129
134
|
className?: string;
|
|
135
|
+
/**
|
|
136
|
+
* Render mode for the card:
|
|
137
|
+
* - `"deal"` (default) — full opportunity info with task accordion.
|
|
138
|
+
* - `"task"` — compact task-first layout (next task, progress, deal ref).
|
|
139
|
+
*/
|
|
140
|
+
viewMode?: "deal" | "task";
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
// ---------------------------------------------------------------------------
|
|
@@ -169,11 +180,291 @@ function formatLoanType(type: string): string {
|
|
|
169
180
|
.join(" ");
|
|
170
181
|
}
|
|
171
182
|
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// TaskViewCard — compact task-first card (viewMode === "task")
|
|
185
|
+
//
|
|
186
|
+
// Three states driven by task progress:
|
|
187
|
+
// A) Has next task → shows task title + AI agent + segmented progress
|
|
188
|
+
// B) All tasks done → shows ✓ message + full green progress bar
|
|
189
|
+
// C) No tasks → shows fallback muted text
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
function TaskViewCard({
|
|
193
|
+
customerName,
|
|
194
|
+
loanType,
|
|
195
|
+
loanPurposeLabel,
|
|
196
|
+
amount,
|
|
197
|
+
priority,
|
|
198
|
+
daysSinceColumnChanged,
|
|
199
|
+
warningDays,
|
|
200
|
+
priorityDays,
|
|
201
|
+
tasks = [],
|
|
202
|
+
nextTask,
|
|
203
|
+
onCardClick,
|
|
204
|
+
onViewDetails,
|
|
205
|
+
onTaskToggle,
|
|
206
|
+
onMarkAsDone,
|
|
207
|
+
onMoveToNextStage,
|
|
208
|
+
onChangePriority,
|
|
209
|
+
onDelete,
|
|
210
|
+
onPutOnHold,
|
|
211
|
+
isSubmitting = false,
|
|
212
|
+
draggable = false,
|
|
213
|
+
onDragStart,
|
|
214
|
+
className,
|
|
215
|
+
}: OpportunityCardProps): React.JSX.Element {
|
|
216
|
+
const resolvedPriority = resolvePriority(
|
|
217
|
+
daysSinceColumnChanged,
|
|
218
|
+
warningDays,
|
|
219
|
+
priorityDays,
|
|
220
|
+
priority,
|
|
221
|
+
);
|
|
222
|
+
const priorityColor = PRIORITY_COLORS[resolvedPriority];
|
|
223
|
+
|
|
224
|
+
const completedCount = tasks.filter((t) => t.completed).length;
|
|
225
|
+
const totalCount = tasks.length;
|
|
226
|
+
const hasTasks = totalCount > 0;
|
|
227
|
+
const allDone = hasTasks && completedCount === totalCount;
|
|
228
|
+
|
|
229
|
+
// Find the next pending task to surface AI agent info
|
|
230
|
+
const nextPendingTask = tasks.find((t) => !t.completed);
|
|
231
|
+
const agentName = nextPendingTask?.aiAgentName ?? null;
|
|
232
|
+
|
|
233
|
+
const [subtasksExpanded, setSubtasksExpanded] = useState(false);
|
|
234
|
+
|
|
235
|
+
const hasMenu = onViewDetails || onChangePriority || onPutOnHold || onDelete;
|
|
236
|
+
const stopProp = (e: React.MouseEvent): void => {
|
|
237
|
+
e.stopPropagation();
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Compact deal reference: "John Smith · Buy a House · $850,000"
|
|
241
|
+
const purposeLabel =
|
|
242
|
+
loanPurposeLabel ?? (loanType ? formatLoanType(loanType) : null);
|
|
243
|
+
const dealRef = [
|
|
244
|
+
customerName,
|
|
245
|
+
purposeLabel,
|
|
246
|
+
amount > 0 ? formatCurrency(amount) : null,
|
|
247
|
+
]
|
|
248
|
+
.filter(Boolean)
|
|
249
|
+
.join(" · ");
|
|
250
|
+
|
|
251
|
+
// Task header content (no nested ternary — avoids lint error)
|
|
252
|
+
let taskHeader: React.ReactNode;
|
|
253
|
+
if (allDone) {
|
|
254
|
+
taskHeader = (
|
|
255
|
+
<p
|
|
256
|
+
className="flex items-center gap-1.5 text-sm font-semibold"
|
|
257
|
+
style={{ color: "var(--color-success-text)" }}
|
|
258
|
+
>
|
|
259
|
+
<Check className="size-3.5 shrink-0" />
|
|
260
|
+
All tasks complete
|
|
261
|
+
</p>
|
|
262
|
+
);
|
|
263
|
+
} else if (nextTask) {
|
|
264
|
+
taskHeader = (
|
|
265
|
+
<>
|
|
266
|
+
<div className="flex items-center gap-1.5">
|
|
267
|
+
{agentName && (
|
|
268
|
+
<Bot
|
|
269
|
+
className="size-3.5 shrink-0 text-primary"
|
|
270
|
+
aria-hidden="true"
|
|
271
|
+
/>
|
|
272
|
+
)}
|
|
273
|
+
<p className="truncate text-sm font-semibold leading-snug">
|
|
274
|
+
{nextTask}
|
|
275
|
+
</p>
|
|
276
|
+
</div>
|
|
277
|
+
{agentName && (
|
|
278
|
+
<p className="mt-0.5 text-xs text-muted-foreground">{agentName}</p>
|
|
279
|
+
)}
|
|
280
|
+
</>
|
|
281
|
+
);
|
|
282
|
+
} else {
|
|
283
|
+
taskHeader = (
|
|
284
|
+
<p className="text-xs text-muted-foreground">
|
|
285
|
+
No tasks defined for this stage
|
|
286
|
+
</p>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<div
|
|
292
|
+
className={cn(
|
|
293
|
+
"flex flex-col gap-2.5 border border-border bg-background p-3 text-foreground shadow-sm",
|
|
294
|
+
onCardClick && "cursor-pointer",
|
|
295
|
+
draggable && "cursor-grab active:cursor-grabbing",
|
|
296
|
+
isSubmitting && "opacity-60",
|
|
297
|
+
className,
|
|
298
|
+
)}
|
|
299
|
+
data-slot="opportunity-card"
|
|
300
|
+
draggable={draggable}
|
|
301
|
+
onDragStart={onDragStart}
|
|
302
|
+
onClick={onCardClick}
|
|
303
|
+
>
|
|
304
|
+
{/* ── Task header + priority dot ── */}
|
|
305
|
+
<div className="flex items-start gap-2">
|
|
306
|
+
<div className="min-w-0 flex-1">{taskHeader}</div>
|
|
307
|
+
<span
|
|
308
|
+
role="img"
|
|
309
|
+
className="mt-0.5 inline-block size-2.5 shrink-0 rounded-full"
|
|
310
|
+
style={{ backgroundColor: priorityColor }}
|
|
311
|
+
aria-label={`Priority: ${resolvedPriority}`}
|
|
312
|
+
/>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
{/* ── Progress (only when tasks exist) ── */}
|
|
316
|
+
{hasTasks && (
|
|
317
|
+
<div className="flex items-center gap-2">
|
|
318
|
+
<span className="shrink-0 tabular-nums text-xs text-muted-foreground">
|
|
319
|
+
{completedCount}/{totalCount}
|
|
320
|
+
</span>
|
|
321
|
+
<Progress
|
|
322
|
+
value={(completedCount / totalCount) * 100}
|
|
323
|
+
className={cn(
|
|
324
|
+
"h-1.5 flex-1",
|
|
325
|
+
allDone && "[&_[data-slot=progress-indicator]]:bg-success",
|
|
326
|
+
)}
|
|
327
|
+
/>
|
|
328
|
+
</div>
|
|
329
|
+
)}
|
|
330
|
+
|
|
331
|
+
{/* ── Collapsible subtask list ── */}
|
|
332
|
+
{hasTasks && (
|
|
333
|
+
<div
|
|
334
|
+
className="space-y-1 border-t border-border pt-2"
|
|
335
|
+
onClick={stopProp}
|
|
336
|
+
role="presentation"
|
|
337
|
+
onKeyDown={(e) => e.stopPropagation()}
|
|
338
|
+
>
|
|
339
|
+
<button
|
|
340
|
+
type="button"
|
|
341
|
+
onClick={() => setSubtasksExpanded((v) => !v)}
|
|
342
|
+
className="flex w-full items-center justify-between text-xs text-muted-foreground hover:text-foreground"
|
|
343
|
+
>
|
|
344
|
+
<span className="flex items-center gap-1">
|
|
345
|
+
{subtasksExpanded ? (
|
|
346
|
+
<ChevronDown className="size-3" />
|
|
347
|
+
) : (
|
|
348
|
+
<ChevronRight className="size-3" />
|
|
349
|
+
)}
|
|
350
|
+
Subtasks
|
|
351
|
+
</span>
|
|
352
|
+
<span>
|
|
353
|
+
{completedCount} / {totalCount}
|
|
354
|
+
</span>
|
|
355
|
+
</button>
|
|
356
|
+
{subtasksExpanded && (
|
|
357
|
+
<ul className="space-y-1.5 pt-1">
|
|
358
|
+
{tasks.map((task) => (
|
|
359
|
+
<TaskCheckItem
|
|
360
|
+
key={task.id}
|
|
361
|
+
title={task.title}
|
|
362
|
+
completed={task.completed}
|
|
363
|
+
aiAgentName={task.aiAgentName}
|
|
364
|
+
onToggle={() => onTaskToggle?.(task.id)}
|
|
365
|
+
size="xs"
|
|
366
|
+
/>
|
|
367
|
+
))}
|
|
368
|
+
</ul>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
)}
|
|
372
|
+
|
|
373
|
+
{/* ── Deal reference line ── */}
|
|
374
|
+
<p className="truncate text-xs text-muted-foreground">{dealRef}</p>
|
|
375
|
+
|
|
376
|
+
{/* ── Footer: days badge + 3-dot menu ── */}
|
|
377
|
+
<div className="flex items-center gap-1.5" onClick={stopProp}>
|
|
378
|
+
{daysSinceColumnChanged !== undefined && (
|
|
379
|
+
<Badge variant="secondary" className="px-1.5 text-[10px] font-normal">
|
|
380
|
+
{daysSinceColumnChanged}d
|
|
381
|
+
</Badge>
|
|
382
|
+
)}
|
|
383
|
+
{hasMenu && (
|
|
384
|
+
<div className="ml-auto">
|
|
385
|
+
<DropdownMenu>
|
|
386
|
+
<DropdownMenuTrigger
|
|
387
|
+
className={cn(
|
|
388
|
+
buttonVariants({ variant: "ghost", size: "icon" }),
|
|
389
|
+
"size-7 shrink-0",
|
|
390
|
+
)}
|
|
391
|
+
aria-label="Opportunity actions"
|
|
392
|
+
>
|
|
393
|
+
<MoreVertical className="size-4" />
|
|
394
|
+
</DropdownMenuTrigger>
|
|
395
|
+
<DropdownMenuContent align="end">
|
|
396
|
+
{onViewDetails && (
|
|
397
|
+
<DropdownMenuItem onClick={onViewDetails}>
|
|
398
|
+
View details
|
|
399
|
+
</DropdownMenuItem>
|
|
400
|
+
)}
|
|
401
|
+
{onChangePriority && (
|
|
402
|
+
<DropdownMenuItem onClick={onChangePriority}>
|
|
403
|
+
Change priority
|
|
404
|
+
</DropdownMenuItem>
|
|
405
|
+
)}
|
|
406
|
+
{onPutOnHold && (
|
|
407
|
+
<DropdownMenuItem onClick={onPutOnHold}>
|
|
408
|
+
Put on hold
|
|
409
|
+
</DropdownMenuItem>
|
|
410
|
+
)}
|
|
411
|
+
{onDelete && (
|
|
412
|
+
<>
|
|
413
|
+
<DropdownMenuSeparator />
|
|
414
|
+
<DropdownMenuItem onClick={onDelete} variant="destructive">
|
|
415
|
+
Delete
|
|
416
|
+
</DropdownMenuItem>
|
|
417
|
+
</>
|
|
418
|
+
)}
|
|
419
|
+
</DropdownMenuContent>
|
|
420
|
+
</DropdownMenu>
|
|
421
|
+
</div>
|
|
422
|
+
)}
|
|
423
|
+
</div>
|
|
424
|
+
|
|
425
|
+
{/* ── Action button ── */}
|
|
426
|
+
{allDone && onMoveToNextStage && (
|
|
427
|
+
<div onClick={stopProp}>
|
|
428
|
+
<Separator />
|
|
429
|
+
<div className="pt-2">
|
|
430
|
+
<Button
|
|
431
|
+
variant="outline"
|
|
432
|
+
size="sm"
|
|
433
|
+
className="w-full text-caption"
|
|
434
|
+
onClick={onMoveToNextStage}
|
|
435
|
+
disabled={isSubmitting}
|
|
436
|
+
>
|
|
437
|
+
Move to next stage
|
|
438
|
+
</Button>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
)}
|
|
442
|
+
{!allDone && nextTask && onMarkAsDone && (
|
|
443
|
+
<div onClick={stopProp}>
|
|
444
|
+
<Separator />
|
|
445
|
+
<div className="pt-2">
|
|
446
|
+
<Button
|
|
447
|
+
variant="default"
|
|
448
|
+
size="sm"
|
|
449
|
+
className="w-full text-caption"
|
|
450
|
+
onClick={onMarkAsDone}
|
|
451
|
+
disabled={isSubmitting}
|
|
452
|
+
>
|
|
453
|
+
Mark as done
|
|
454
|
+
</Button>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
)}
|
|
458
|
+
</div>
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
172
462
|
// ---------------------------------------------------------------------------
|
|
173
463
|
// OpportunityCard
|
|
174
464
|
// ---------------------------------------------------------------------------
|
|
175
465
|
|
|
176
466
|
export function OpportunityCard({
|
|
467
|
+
id,
|
|
177
468
|
customerName,
|
|
178
469
|
customerPhone,
|
|
179
470
|
customerEmail,
|
|
@@ -203,7 +494,41 @@ export function OpportunityCard({
|
|
|
203
494
|
draggable = false,
|
|
204
495
|
onDragStart,
|
|
205
496
|
className,
|
|
497
|
+
viewMode = "deal",
|
|
206
498
|
}: OpportunityCardProps) {
|
|
499
|
+
if (viewMode === "task") {
|
|
500
|
+
return (
|
|
501
|
+
<TaskViewCard
|
|
502
|
+
customerName={customerName}
|
|
503
|
+
customerPhone={customerPhone}
|
|
504
|
+
customerEmail={customerEmail}
|
|
505
|
+
additionalContacts={additionalContacts}
|
|
506
|
+
loanType={loanType}
|
|
507
|
+
loanPurposeLabel={loanPurposeLabel}
|
|
508
|
+
amount={amount}
|
|
509
|
+
date={date}
|
|
510
|
+
priority={priority}
|
|
511
|
+
daysSinceColumnChanged={daysSinceColumnChanged}
|
|
512
|
+
warningDays={warningDays}
|
|
513
|
+
priorityDays={priorityDays}
|
|
514
|
+
tasks={tasks}
|
|
515
|
+
nextTask={nextTask}
|
|
516
|
+
onCardClick={onCardClick}
|
|
517
|
+
onViewDetails={onViewDetails}
|
|
518
|
+
onTaskToggle={onTaskToggle}
|
|
519
|
+
onMarkAsDone={onMarkAsDone}
|
|
520
|
+
onMoveToNextStage={onMoveToNextStage}
|
|
521
|
+
onChangePriority={onChangePriority}
|
|
522
|
+
onDelete={onDelete}
|
|
523
|
+
onPutOnHold={onPutOnHold}
|
|
524
|
+
isSubmitting={isSubmitting}
|
|
525
|
+
draggable={draggable}
|
|
526
|
+
onDragStart={onDragStart}
|
|
527
|
+
className={className}
|
|
528
|
+
/>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
207
532
|
const resolvedPriority = resolvePriority(
|
|
208
533
|
daysSinceColumnChanged,
|
|
209
534
|
warningDays,
|
|
@@ -218,7 +543,9 @@ export function OpportunityCard({
|
|
|
218
543
|
const hasActions = onMarkAsDone || onMoveToNextStage;
|
|
219
544
|
const hasMenu = onViewDetails || onChangePriority || onPutOnHold || onDelete;
|
|
220
545
|
|
|
221
|
-
const stopProp = (e: React.MouseEvent) =>
|
|
546
|
+
const stopProp = (e: React.MouseEvent): void => {
|
|
547
|
+
e.stopPropagation();
|
|
548
|
+
};
|
|
222
549
|
|
|
223
550
|
return (
|
|
224
551
|
<div
|