Spaces:
Running
Running
| import { useState } from 'react'; | |
| import { Button, SegmentInput, Checkbox, Spinner } from '@ifrc-go/ui'; | |
| import styles from './ExportModal.module.css'; | |
| interface ExportModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| onExport: (mode: 'standard' | 'fine-tuning', selectedTypes: string[]) => void; | |
| filteredCount: number; | |
| totalCount: number; | |
| hasFilters: boolean; | |
| crisisMapsCount: number; | |
| droneImagesCount: number; | |
| isLoading?: boolean; | |
| exportSuccess?: boolean; | |
| variant?: 'bulk' | 'single'; | |
| onNavigateToList?: () => void; | |
| onNavigateAndExport?: () => void; | |
| } | |
| export default function ExportModal({ | |
| isOpen, | |
| onClose, | |
| onExport, | |
| crisisMapsCount, | |
| droneImagesCount, | |
| isLoading = false, | |
| exportSuccess = false, | |
| variant = 'bulk', | |
| onNavigateAndExport | |
| }: ExportModalProps) { | |
| const [exportMode, setExportMode] = useState<'standard' | 'fine-tuning'>('standard'); | |
| const [trainSplit, setTrainSplit] = useState(80); | |
| const [testSplit, setTestSplit] = useState(10); | |
| const [valSplit, setValSplit] = useState(10); | |
| const [crisisMapsSelected, setCrisisMapsSelected] = useState(true); | |
| const [droneImagesSelected, setDroneImagesSelected] = useState(true); | |
| const handleExport = () => { | |
| if (variant === 'single') { | |
| // For single item export, just export immediately | |
| onExport(exportMode, ['crisis_map', 'drone_image']); // Export regardless of type | |
| return; | |
| } | |
| // For bulk export, check selections | |
| if (!crisisMapsSelected && !droneImagesSelected) { | |
| alert('Please select at least one image type to export.'); | |
| return; | |
| } | |
| const selectedTypes: string[] = []; | |
| if (crisisMapsSelected) selectedTypes.push('crisis_map'); | |
| if (droneImagesSelected) selectedTypes.push('drone_image'); | |
| onExport(exportMode, selectedTypes); | |
| }; | |
| const handleClose = () => { | |
| onClose(); | |
| }; | |
| if (!isOpen) return null; | |
| // Single item export UI | |
| if (variant === 'single') { | |
| return ( | |
| <div className={styles.fullSizeModalOverlay} onClick={handleClose}> | |
| <div className={styles.fullSizeModalContent} onClick={(e) => e.stopPropagation()}> | |
| {isLoading && ( | |
| <div className={styles.loadingOverlay}> | |
| <div className="flex flex-col items-center gap-4"> | |
| <Spinner className="text-ifrcRed" /> | |
| <div className="text-lg font-medium">Exporting...</div> | |
| <div className="text-sm text-gray-600">This might take a few seconds</div> | |
| </div> | |
| </div> | |
| )} | |
| {exportSuccess && ( | |
| <div className={styles.loadingOverlay}> | |
| <div className="flex flex-col items-center gap-4"> | |
| <div className="text-lg font-medium">Export Successful!</div> | |
| <div className="text-sm text-gray-600">Your dataset has been downloaded</div> | |
| <Button | |
| name="close-export-success" | |
| onClick={handleClose} | |
| className="mt-4" | |
| > | |
| Close | |
| </Button> | |
| </div> | |
| </div> | |
| )} | |
| <div className={styles.ratingWarningContent}> | |
| <h3 className={styles.ratingWarningTitle}>Export Single Item</h3> | |
| <div className={styles.singleExportMessage}> | |
| <p>This only exports the 1 item currently on display.</p> | |
| <p>You may export the entire dataset from the "list view" here:</p> | |
| </div> | |
| <div className={styles.navigateButtonContainer}> | |
| <Button | |
| name="navigate-to-list" | |
| variant="secondary" | |
| onClick={onNavigateAndExport} | |
| > | |
| Navigate to List View | |
| </Button> | |
| </div> | |
| <div className={styles.ratingWarningButtons}> | |
| <Button | |
| name="continue-export" | |
| onClick={handleExport} | |
| disabled={isLoading} | |
| > | |
| {isLoading ? ( | |
| <div className="flex items-center gap-2"> | |
| <Spinner className="text-white" /> | |
| Exporting... | |
| </div> | |
| ) : ( | |
| 'Continue' | |
| )} | |
| </Button> | |
| <Button | |
| name="cancel-export" | |
| variant="tertiary" | |
| onClick={handleClose} | |
| disabled={isLoading} | |
| > | |
| Cancel | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Bulk export UI (original functionality) | |
| return ( | |
| <div className={styles.fullSizeModalOverlay} onClick={handleClose}> | |
| <div className={styles.fullSizeModalContent} onClick={(e) => e.stopPropagation()}> | |
| {isLoading && ( | |
| <div className={styles.loadingOverlay}> | |
| <div className="flex flex-col items-center gap-4"> | |
| <Spinner className="text-ifrcRed" /> | |
| <div className="text-lg font-medium">Exporting...</div> | |
| <div className="text-sm text-gray-600">This might take a few seconds</div> | |
| </div> | |
| </div> | |
| )} | |
| {exportSuccess && ( | |
| <div className={styles.loadingOverlay}> | |
| <div className="flex flex-col items-center gap-4"> | |
| <div className="text-lg font-medium">Export Successful!</div> | |
| <div className="text-sm text-gray-600">Your dataset has been downloaded</div> | |
| <Button | |
| name="close-export-success" | |
| onClick={handleClose} | |
| className="mt-4" | |
| > | |
| Close | |
| </Button> | |
| </div> | |
| </div> | |
| )} | |
| <div className={styles.ratingWarningContent}> | |
| <h3 className={styles.ratingWarningTitle}>Export Dataset</h3> | |
| {/* Export Mode Switch */} | |
| <div className={styles.exportModeSection}> | |
| <SegmentInput | |
| name="export-mode" | |
| value={exportMode} | |
| onChange={(value) => { | |
| if (value === 'standard' || value === 'fine-tuning') { | |
| setExportMode(value); | |
| } | |
| }} | |
| options={[ | |
| { key: 'standard' as const, label: 'Standard' }, | |
| { key: 'fine-tuning' as const, label: 'Fine-tuning' } | |
| ]} | |
| keySelector={(o) => o.key} | |
| labelSelector={(o) => o.label} | |
| disabled={isLoading} | |
| /> | |
| </div> | |
| {/* Train/Test/Val Split Configuration - Only show for Fine-tuning mode */} | |
| {exportMode === 'fine-tuning' && ( | |
| <div className={styles.splitConfigSection}> | |
| <div className={styles.splitConfigTitle}>Dataset Split Configuration</div> | |
| <div className={styles.splitInputsContainer}> | |
| <div className={styles.splitInputGroup}> | |
| <label htmlFor="train-split" className={styles.splitInputLabel}>Train (%)</label> | |
| <input | |
| id="train-split" | |
| type="number" | |
| min="0" | |
| max="100" | |
| value={trainSplit} | |
| onChange={(e) => { | |
| const newTrain = parseInt(e.target.value) || 0; | |
| const remaining = 100 - newTrain; | |
| if (remaining >= 0) { | |
| setTrainSplit(newTrain); | |
| if (testSplit + valSplit > remaining) { | |
| setTestSplit(Math.floor(remaining / 2)); | |
| setValSplit(remaining - Math.floor(remaining / 2)); | |
| } | |
| } | |
| }} | |
| className={styles.splitInput} | |
| disabled={isLoading} | |
| /> | |
| </div> | |
| <div className={styles.splitInputGroup}> | |
| <label htmlFor="test-split" className={styles.splitInputLabel}>Test (%)</label> | |
| <input | |
| id="test-split" | |
| type="number" | |
| min="0" | |
| max="100" | |
| value={testSplit} | |
| onChange={(e) => { | |
| const newTest = parseInt(e.target.value) || 0; | |
| const remaining = 100 - trainSplit - newTest; | |
| if (remaining >= 0) { | |
| setTestSplit(newTest); | |
| setValSplit(remaining); | |
| } | |
| }} | |
| className={styles.splitInput} | |
| disabled={isLoading} | |
| /> | |
| </div> | |
| <div className={styles.splitInputGroup}> | |
| <label htmlFor="val-split" className={styles.splitInputLabel}>Val (%)</label> | |
| <input | |
| id="val-split" | |
| type="number" | |
| min="0" | |
| max="100" | |
| value={valSplit} | |
| onChange={(e) => { | |
| const newVal = parseInt(e.target.value) || 0; | |
| const remaining = 100 - trainSplit - newVal; | |
| if (remaining >= 0) { | |
| setValSplit(newVal); | |
| setTestSplit(remaining); | |
| } | |
| }} | |
| className={styles.splitInput} | |
| disabled={isLoading} | |
| /> | |
| </div> | |
| </div> | |
| {trainSplit + testSplit + valSplit !== 100 && ( | |
| <div className={styles.splitTotal}> | |
| <span className={styles.splitTotalError}>Must equal 100%</span> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| <div className={styles.checkboxesContainer}> | |
| <div className="flex items-center gap-3"> | |
| <Checkbox | |
| name="crisis-maps" | |
| label={`Crisis Maps (${crisisMapsCount} images)`} | |
| value={crisisMapsSelected} | |
| onChange={(value) => setCrisisMapsSelected(value)} | |
| disabled={isLoading} | |
| /> | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| <Checkbox | |
| name="drone-images" | |
| label={`Drone Images (${droneImagesCount} images)`} | |
| value={droneImagesSelected} | |
| onChange={(value) => setDroneImagesSelected(value)} | |
| disabled={isLoading} | |
| /> | |
| </div> | |
| </div> | |
| <div className={styles.ratingWarningButtons}> | |
| <Button | |
| name="confirm-export" | |
| onClick={handleExport} | |
| disabled={isLoading} | |
| > | |
| {isLoading ? ( | |
| <div className="flex items-center gap-2"> | |
| <Spinner className="text-white" /> | |
| Exporting... | |
| </div> | |
| ) : ( | |
| 'Export Selected' | |
| )} | |
| </Button> | |
| <Button | |
| name="cancel-export" | |
| variant="tertiary" | |
| onClick={handleClose} | |
| disabled={isLoading} | |
| > | |
| Cancel | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |