@wealthx/shadcn 1.5.1 → 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 +118 -118
- package/CHANGELOG.md +6 -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 +5 -4
- 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,5 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AlertCircleIcon,
|
|
4
|
+
CheckCircle2Icon,
|
|
5
|
+
CircleAlertIcon,
|
|
6
|
+
FileTextIcon,
|
|
7
|
+
Trash2Icon,
|
|
8
|
+
} from "lucide-react";
|
|
3
9
|
import {
|
|
4
10
|
Dialog,
|
|
5
11
|
DialogContent,
|
|
@@ -19,6 +25,19 @@ import {
|
|
|
19
25
|
TableHeader,
|
|
20
26
|
TableRow,
|
|
21
27
|
} from "@/components/ui/table";
|
|
28
|
+
import {
|
|
29
|
+
Tooltip,
|
|
30
|
+
TooltipContent,
|
|
31
|
+
TooltipProvider,
|
|
32
|
+
TooltipTrigger,
|
|
33
|
+
} from "@/components/ui/tooltip";
|
|
34
|
+
import {
|
|
35
|
+
Select,
|
|
36
|
+
SelectContent,
|
|
37
|
+
SelectItem,
|
|
38
|
+
SelectTrigger,
|
|
39
|
+
SelectValue,
|
|
40
|
+
} from "@/components/ui/select";
|
|
22
41
|
import { cn } from "@/lib/utils";
|
|
23
42
|
|
|
24
43
|
// ---------------------------------------------------------------------------
|
|
@@ -32,12 +51,25 @@ export type FilePreviewState =
|
|
|
32
51
|
| "error"
|
|
33
52
|
| "importing";
|
|
34
53
|
|
|
54
|
+
export type CsvRowStatus = "pending" | "success" | "failed";
|
|
55
|
+
|
|
35
56
|
export interface CsvPreviewColumn {
|
|
36
57
|
key: string;
|
|
37
58
|
/** Display label shown in the column header. */
|
|
38
59
|
label: string;
|
|
39
60
|
}
|
|
40
61
|
|
|
62
|
+
export interface CsvPreviewRow extends Record<string, string | undefined> {
|
|
63
|
+
_status?: CsvRowStatus;
|
|
64
|
+
/** Shown in a tooltip when status is "failed". */
|
|
65
|
+
_statusMessage?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface StaffOption {
|
|
69
|
+
id: string;
|
|
70
|
+
name: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
export interface FilePreviewDialogProps {
|
|
42
74
|
open: boolean;
|
|
43
75
|
onOpenChange: (open: boolean) => void;
|
|
@@ -48,22 +80,33 @@ export interface FilePreviewDialogProps {
|
|
|
48
80
|
/** Ordered list of columns to display. */
|
|
49
81
|
columns?: CsvPreviewColumn[];
|
|
50
82
|
/** Data rows — keys match CsvPreviewColumn.key. */
|
|
51
|
-
rows?:
|
|
52
|
-
/**
|
|
53
|
-
* Called when the user reorders a column via drag-and-drop.
|
|
54
|
-
* sourceIndex and targetIndex refer to positions in the columns array.
|
|
55
|
-
*/
|
|
56
|
-
onColumnReorder?: (sourceIndex: number, targetIndex: number) => void;
|
|
83
|
+
rows?: CsvPreviewRow[];
|
|
57
84
|
/** Called when the user edits a cell inline. */
|
|
58
85
|
onRowChange?: (rowIndex: number, key: string, value: string) => void;
|
|
86
|
+
/** Called when the user clicks the delete icon on a row. */
|
|
87
|
+
onRowDelete?: (rowIndex: number) => void;
|
|
59
88
|
/** Called when the user clicks "Import". */
|
|
60
89
|
onImport?: () => void;
|
|
90
|
+
/** Called when the user clicks "Cancel" during import. */
|
|
91
|
+
onCancelImport?: () => void;
|
|
61
92
|
/** 0–100 progress value shown in the importing state. */
|
|
62
93
|
importProgress?: number;
|
|
63
94
|
/** Total rows in the file (shown in the preview header). */
|
|
64
95
|
totalRows?: number;
|
|
65
96
|
/** Rows that passed validation (shown in the preview header). */
|
|
66
97
|
validRows?: number;
|
|
98
|
+
/** Rows per page for the preview table. Defaults to 10. */
|
|
99
|
+
pageSize?: number;
|
|
100
|
+
/**
|
|
101
|
+
* List of staff members available for assignment.
|
|
102
|
+
* When provided, a staff selector is rendered above the table.
|
|
103
|
+
* Import is blocked until a staff member is selected.
|
|
104
|
+
*/
|
|
105
|
+
staffOptions?: StaffOption[];
|
|
106
|
+
/** Currently selected staff ID. */
|
|
107
|
+
selectedStaffId?: string;
|
|
108
|
+
/** Called when the user picks a staff member. */
|
|
109
|
+
onStaffSelect?: (staffId: string) => void;
|
|
67
110
|
className?: string;
|
|
68
111
|
}
|
|
69
112
|
|
|
@@ -126,20 +169,64 @@ function ErrorState({ message }: { message?: string }) {
|
|
|
126
169
|
);
|
|
127
170
|
}
|
|
128
171
|
|
|
129
|
-
function ImportingState({
|
|
172
|
+
function ImportingState({
|
|
173
|
+
progress,
|
|
174
|
+
successCount,
|
|
175
|
+
failedCount,
|
|
176
|
+
}: {
|
|
177
|
+
progress: number;
|
|
178
|
+
successCount: number;
|
|
179
|
+
failedCount: number;
|
|
180
|
+
}) {
|
|
130
181
|
return (
|
|
131
|
-
<div className="flex flex-col items-center gap-4 py-
|
|
182
|
+
<div className="flex flex-col items-center gap-4 py-8">
|
|
132
183
|
<Spinner className="size-6" />
|
|
133
184
|
<div className="w-full max-w-sm space-y-2">
|
|
134
185
|
<Progress value={progress} className="h-1.5" />
|
|
135
|
-
<
|
|
136
|
-
Importing… {progress}
|
|
137
|
-
|
|
186
|
+
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
|
187
|
+
<span>Importing… {progress}%</span>
|
|
188
|
+
{(successCount > 0 || failedCount > 0) && (
|
|
189
|
+
<span>
|
|
190
|
+
{successCount} success · {failedCount} failed
|
|
191
|
+
</span>
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
138
194
|
</div>
|
|
139
195
|
</div>
|
|
140
196
|
);
|
|
141
197
|
}
|
|
142
198
|
|
|
199
|
+
function RowStatusCell({ row }: { row: CsvPreviewRow }) {
|
|
200
|
+
const { _status, _statusMessage } = row;
|
|
201
|
+
|
|
202
|
+
if (_status === "success") {
|
|
203
|
+
return (
|
|
204
|
+
<div className="flex items-center justify-center h-full px-2">
|
|
205
|
+
<CheckCircle2Icon className="size-4 text-success" />
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (_status === "failed") {
|
|
211
|
+
return (
|
|
212
|
+
<TooltipProvider>
|
|
213
|
+
<Tooltip>
|
|
214
|
+
<TooltipTrigger asChild>
|
|
215
|
+
<div className="flex items-center justify-center h-full px-2 cursor-default">
|
|
216
|
+
<CircleAlertIcon className="size-4 text-destructive" />
|
|
217
|
+
</div>
|
|
218
|
+
</TooltipTrigger>
|
|
219
|
+
{_statusMessage && (
|
|
220
|
+
<TooltipContent side="top">{_statusMessage}</TooltipContent>
|
|
221
|
+
)}
|
|
222
|
+
</Tooltip>
|
|
223
|
+
</TooltipProvider>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
143
230
|
// ---------------------------------------------------------------------------
|
|
144
231
|
// Main component
|
|
145
232
|
// ---------------------------------------------------------------------------
|
|
@@ -152,43 +239,41 @@ export function FilePreviewDialog({
|
|
|
152
239
|
errorMessage,
|
|
153
240
|
columns = [],
|
|
154
241
|
rows = [],
|
|
155
|
-
onColumnReorder,
|
|
156
242
|
onRowChange,
|
|
243
|
+
onRowDelete,
|
|
157
244
|
onImport,
|
|
245
|
+
onCancelImport,
|
|
158
246
|
importProgress = 0,
|
|
159
247
|
totalRows,
|
|
160
248
|
validRows,
|
|
249
|
+
pageSize = 10,
|
|
250
|
+
staffOptions,
|
|
251
|
+
selectedStaffId,
|
|
252
|
+
onStaffSelect,
|
|
161
253
|
className,
|
|
162
254
|
}: FilePreviewDialogProps) {
|
|
163
|
-
|
|
164
|
-
const dragSourceRef = React.useRef<number | null>(null);
|
|
165
|
-
const [dragOverIndex, setDragOverIndex] = React.useState<number | null>(null);
|
|
255
|
+
const [page, setPage] = React.useState(0);
|
|
166
256
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
257
|
+
// Reset page when rows change
|
|
258
|
+
React.useEffect(() => {
|
|
259
|
+
setPage(0);
|
|
260
|
+
}, [rows.length]);
|
|
170
261
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function handleDrop(targetIndex: number) {
|
|
177
|
-
const source = dragSourceRef.current;
|
|
178
|
-
if (source !== null && source !== targetIndex) {
|
|
179
|
-
onColumnReorder?.(source, targetIndex);
|
|
180
|
-
}
|
|
181
|
-
dragSourceRef.current = null;
|
|
182
|
-
setDragOverIndex(null);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function handleDragEnd() {
|
|
186
|
-
dragSourceRef.current = null;
|
|
187
|
-
setDragOverIndex(null);
|
|
188
|
-
}
|
|
262
|
+
const totalPages = Math.ceil(rows.length / pageSize);
|
|
263
|
+
const pagedRows = rows.slice(page * pageSize, (page + 1) * pageSize);
|
|
264
|
+
const pageStart = page * pageSize; // used for row index display
|
|
189
265
|
|
|
190
266
|
const isImporting = state === "importing";
|
|
191
|
-
const
|
|
267
|
+
const hasStaffSelector = !!staffOptions && staffOptions.length > 0;
|
|
268
|
+
// Import is blocked when staff selection is required but none is chosen yet
|
|
269
|
+
const canImport =
|
|
270
|
+
state === "preview" &&
|
|
271
|
+
rows.length > 0 &&
|
|
272
|
+
(!hasStaffSelector || !!selectedStaffId);
|
|
273
|
+
|
|
274
|
+
const successCount = rows.filter((r) => r._status === "success").length;
|
|
275
|
+
const failedCount = rows.filter((r) => r._status === "failed").length;
|
|
276
|
+
const hasStatus = rows.some((r) => r._status !== undefined);
|
|
192
277
|
|
|
193
278
|
return (
|
|
194
279
|
<Dialog open={open} onOpenChange={isImporting ? undefined : onOpenChange}>
|
|
@@ -208,83 +293,198 @@ export function FilePreviewDialog({
|
|
|
208
293
|
total
|
|
209
294
|
</span>
|
|
210
295
|
)}
|
|
296
|
+
{hasStatus && (
|
|
297
|
+
<span>
|
|
298
|
+
{successCount} success · {failedCount} failed
|
|
299
|
+
</span>
|
|
300
|
+
)}
|
|
211
301
|
</div>
|
|
212
302
|
)}
|
|
213
303
|
|
|
214
304
|
{/* Content by state */}
|
|
215
305
|
{state === "loading" && <LoadingState columnCount={columns.length} />}
|
|
216
|
-
|
|
217
306
|
{state === "empty" && <EmptyState />}
|
|
218
|
-
|
|
219
307
|
{state === "error" && <ErrorState message={errorMessage} />}
|
|
220
|
-
|
|
221
|
-
|
|
308
|
+
{state === "importing" && (
|
|
309
|
+
<ImportingState
|
|
310
|
+
progress={importProgress}
|
|
311
|
+
successCount={successCount}
|
|
312
|
+
failedCount={failedCount}
|
|
313
|
+
/>
|
|
314
|
+
)}
|
|
222
315
|
|
|
223
316
|
{state === "preview" && (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
317
|
+
<>
|
|
318
|
+
{/* Staff assignment — shown when caller provides staffOptions */}
|
|
319
|
+
{hasStaffSelector && (
|
|
320
|
+
<div className="flex flex-col gap-1">
|
|
321
|
+
<label className="text-label-medium text-foreground">
|
|
322
|
+
Assign staff{" "}
|
|
323
|
+
<span className="text-destructive" aria-hidden="true">
|
|
324
|
+
*
|
|
325
|
+
</span>
|
|
326
|
+
</label>
|
|
327
|
+
<Select
|
|
328
|
+
value={selectedStaffId ?? ""}
|
|
329
|
+
onValueChange={(id) => onStaffSelect?.(id)}
|
|
330
|
+
>
|
|
331
|
+
<SelectTrigger className="w-full">
|
|
332
|
+
<SelectValue placeholder="Select a staff member" />
|
|
333
|
+
</SelectTrigger>
|
|
334
|
+
<SelectContent>
|
|
335
|
+
{staffOptions!.map((s) => (
|
|
336
|
+
<SelectItem key={s.id} value={s.id}>
|
|
337
|
+
{s.name}
|
|
338
|
+
</SelectItem>
|
|
339
|
+
))}
|
|
340
|
+
</SelectContent>
|
|
341
|
+
</Select>
|
|
342
|
+
<p className="text-xs text-muted-foreground">
|
|
343
|
+
All contacts in this import will be assigned to the selected
|
|
344
|
+
staff member.
|
|
345
|
+
</p>
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
251
348
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
349
|
+
<div className="max-h-[360px] overflow-auto border border-border">
|
|
350
|
+
<Table>
|
|
351
|
+
<TableHeader className="sticky top-0 z-10 bg-muted/80 backdrop-blur-sm">
|
|
352
|
+
<TableRow>
|
|
353
|
+
{/* Index column */}
|
|
354
|
+
<TableHead className="w-10 text-center select-none">
|
|
355
|
+
#
|
|
356
|
+
</TableHead>
|
|
255
357
|
{columns.map((col) => (
|
|
256
|
-
<
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
value={row[col.key] ?? ""}
|
|
260
|
-
onChange={(e) =>
|
|
261
|
-
onRowChange?.(rowIdx, col.key, e.target.value)
|
|
262
|
-
}
|
|
263
|
-
className={cn(
|
|
264
|
-
"w-full bg-transparent px-4 py-3 text-sm text-foreground",
|
|
265
|
-
"focus:outline-none focus:ring-1 focus:ring-inset focus:ring-primary",
|
|
266
|
-
)}
|
|
267
|
-
/>
|
|
268
|
-
</TableCell>
|
|
358
|
+
<TableHead key={col.key} className="select-none">
|
|
359
|
+
{col.label}
|
|
360
|
+
</TableHead>
|
|
269
361
|
))}
|
|
362
|
+
{/* Status column — only shown when any row has status */}
|
|
363
|
+
{hasStatus && (
|
|
364
|
+
<TableHead className="w-16 text-center select-none">
|
|
365
|
+
Status
|
|
366
|
+
</TableHead>
|
|
367
|
+
)}
|
|
368
|
+
{/* Delete column */}
|
|
369
|
+
{onRowDelete && <TableHead className="w-10 select-none" />}
|
|
270
370
|
</TableRow>
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
371
|
+
</TableHeader>
|
|
372
|
+
|
|
373
|
+
<TableBody>
|
|
374
|
+
{pagedRows.map((row, pageRowIdx) => {
|
|
375
|
+
const absoluteIdx = pageStart + pageRowIdx;
|
|
376
|
+
return (
|
|
377
|
+
<TableRow
|
|
378
|
+
key={absoluteIdx}
|
|
379
|
+
className={cn(
|
|
380
|
+
row._status === "failed" && "bg-destructive/5",
|
|
381
|
+
row._status === "success" && "bg-success/5",
|
|
382
|
+
)}
|
|
383
|
+
>
|
|
384
|
+
{/* Index cell */}
|
|
385
|
+
<TableCell className="text-center text-xs text-muted-foreground select-none w-10">
|
|
386
|
+
{absoluteIdx + 1}
|
|
387
|
+
</TableCell>
|
|
388
|
+
|
|
389
|
+
{/* Data cells */}
|
|
390
|
+
{columns.map((col) => (
|
|
391
|
+
<TableCell key={col.key} className="p-0">
|
|
392
|
+
<input
|
|
393
|
+
type="text"
|
|
394
|
+
value={row[col.key] ?? ""}
|
|
395
|
+
onChange={(e) =>
|
|
396
|
+
onRowChange?.(
|
|
397
|
+
absoluteIdx,
|
|
398
|
+
col.key,
|
|
399
|
+
e.target.value,
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
className={cn(
|
|
403
|
+
"w-full bg-transparent px-4 py-3 text-sm text-foreground",
|
|
404
|
+
"focus:outline-none focus:ring-1 focus:ring-inset focus:ring-primary",
|
|
405
|
+
)}
|
|
406
|
+
/>
|
|
407
|
+
</TableCell>
|
|
408
|
+
))}
|
|
409
|
+
|
|
410
|
+
{/* Status cell */}
|
|
411
|
+
{hasStatus && (
|
|
412
|
+
<TableCell className="text-center w-16 p-0">
|
|
413
|
+
<RowStatusCell row={row} />
|
|
414
|
+
</TableCell>
|
|
415
|
+
)}
|
|
416
|
+
|
|
417
|
+
{/* Delete cell */}
|
|
418
|
+
{onRowDelete && (
|
|
419
|
+
<TableCell className="w-10 p-0 text-center">
|
|
420
|
+
<Button
|
|
421
|
+
variant="ghost"
|
|
422
|
+
size="icon-sm"
|
|
423
|
+
aria-label={`Delete row ${absoluteIdx + 1}`}
|
|
424
|
+
onClick={() => onRowDelete(absoluteIdx)}
|
|
425
|
+
className="text-muted-foreground hover:text-destructive"
|
|
426
|
+
>
|
|
427
|
+
<Trash2Icon className="size-3.5" />
|
|
428
|
+
</Button>
|
|
429
|
+
</TableCell>
|
|
430
|
+
)}
|
|
431
|
+
</TableRow>
|
|
432
|
+
);
|
|
433
|
+
})}
|
|
434
|
+
</TableBody>
|
|
435
|
+
</Table>
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
{/* Pagination */}
|
|
439
|
+
{totalPages > 1 && (
|
|
440
|
+
<div className="flex items-center justify-between text-xs text-muted-foreground pt-1">
|
|
441
|
+
<span>
|
|
442
|
+
Showing {pageStart + 1}–
|
|
443
|
+
{Math.min(pageStart + pageSize, rows.length)} of {rows.length}
|
|
444
|
+
</span>
|
|
445
|
+
<div className="flex items-center gap-1">
|
|
446
|
+
<Button
|
|
447
|
+
variant="outline"
|
|
448
|
+
size="sm"
|
|
449
|
+
onClick={() => setPage((p) => Math.max(0, p - 1))}
|
|
450
|
+
disabled={page === 0}
|
|
451
|
+
>
|
|
452
|
+
Previous
|
|
453
|
+
</Button>
|
|
454
|
+
<span className="px-2">
|
|
455
|
+
{page + 1} / {totalPages}
|
|
456
|
+
</span>
|
|
457
|
+
<Button
|
|
458
|
+
variant="outline"
|
|
459
|
+
size="sm"
|
|
460
|
+
onClick={() =>
|
|
461
|
+
setPage((p) => Math.min(totalPages - 1, p + 1))
|
|
462
|
+
}
|
|
463
|
+
disabled={page >= totalPages - 1}
|
|
464
|
+
>
|
|
465
|
+
Next
|
|
466
|
+
</Button>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
)}
|
|
470
|
+
</>
|
|
275
471
|
)}
|
|
276
472
|
|
|
277
473
|
<DialogFooter>
|
|
278
|
-
|
|
279
|
-
variant="outline"
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
474
|
+
{isImporting ? (
|
|
475
|
+
<Button variant="outline" onClick={onCancelImport}>
|
|
476
|
+
Cancel Import
|
|
477
|
+
</Button>
|
|
478
|
+
) : (
|
|
479
|
+
<>
|
|
480
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
481
|
+
Cancel
|
|
482
|
+
</Button>
|
|
483
|
+
<Button disabled={!canImport} onClick={onImport}>
|
|
484
|
+
Import
|
|
485
|
+
</Button>
|
|
486
|
+
</>
|
|
487
|
+
)}
|
|
288
488
|
</DialogFooter>
|
|
289
489
|
</DialogContent>
|
|
290
490
|
</Dialog>
|