+ {/* Step 1: Campaign Cards */}
+ {step === 'select-campaign' && (
+
+ {activeCampaigns.length === 0 ? (
+
No active campaigns. Create a campaign first.
+ ) : (
+ activeCampaigns.map(campaign => (
+
+ ))
+ )}
+
+ )}
+
+ {/* Step 2: Upload + Preview */}
+ {step === 'upload-preview' && (
+
+ {csvRows.length === 0 ? (
+
+ ) : (
+ <>
+ {!phoneIsMapped && (
+
+
+ Phone column must be mapped to proceed
+
+ )}
+
+
+ {csvRows.length} rows
+ {validCount} ready
+ {patientMatchCount > 0 && {patientMatchCount} existing patients}
+ {duplicateCount > 0 && {duplicateCount} duplicates}
+ {noPhoneCount > 0 && {noPhoneCount} no phone}
+
+
+
+
+
+
+ {mapping.map(m => (
+ |
+
+ {m.csvHeader}
+
+
+ |
+ ))}
+
+ Patient Match
+ |
+
+
+
+ {rowsWithMatch.slice(0, 20).map((item, i) => (
+
+ {mapping.map(m => (
+ |
+ {item.row[m.csvHeader] ?? ''}
+ |
+ ))}
+
+ {item.matchedPatient ? (
+
+ {item.matchedPatient.fullName?.firstName ?? 'Patient'}
+
+ ) : item.isDuplicate ? (
+ Duplicate
+ ) : !item.hasPhone ? (
+ No phone
+ ) : (
+ New
+ )}
+ |
+
+ ))}
+
+
+ {csvRows.length > 20 && (
+
+ Showing 20 of {csvRows.length} rows
+
+ )}
+
+ >
+ )}
+
+ )}
+
+ {/* Step 3: Importing */}
+ {step === 'importing' && (
+
+
+
Importing leads...
+
{importProgress} of {rowsWithMatch.length}
+
+
+ )}
+
+ {/* Step 4: Done */}
+ {step === 'done' && result && (
+
+
} color="success" theme="light" size="lg" />
+ Import Complete
+
+
+
{result.created}
+
Created
+
+
+
{result.linkedToPatient}
+
Linked to Patients
+
+ {result.skippedDuplicate > 0 && (
+
+
{result.skippedDuplicate}
+
Duplicates
+
+ )}
+ {result.failed > 0 && (
+
+
{result.failed}
+
Failed
+
+ )}
+
+
+ )}
+
+ {step === 'select-campaign' && (
+
+ )}
+ {step === 'upload-preview' && (
+ <>
+
+
+ >
+ )}
+ {step === 'done' && (
+
+ )}
+