erp-platform/ui/src/components/importManager/ImportPreview.tsx
2025-08-12 12:39:09 +03:00

323 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useRef } from 'react'
import { CheckCircle, AlertTriangle, Eye, Play, X } from 'lucide-react'
import { ListFormImportDto } from '@/proxy/imports/models'
import { GridDto } from '@/proxy/form/models'
import { ImportService } from '@/services/import.service'
interface ImportPreviewProps {
session: ListFormImportDto
gridDto: GridDto
onExecute: (
sessionId: string,
listFormCode: string,
selectedRows?: number[],
) => Promise<{ successCount?: number } | void>
loading: boolean
importService: ImportService
onPreviewLoaded?: () => void
}
export const ImportPreview: React.FC<ImportPreviewProps> = ({
session,
gridDto,
onExecute,
loading,
importService,
onPreviewLoaded,
}) => {
const [previewData, setPreviewData] = useState<any>(null)
const [selectedRows, setSelectedRows] = useState<number[]>([])
const [selectAll, setSelectAll] = useState(false)
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
const [lastExecutionResult, setLastExecutionResult] = useState<{
selectedCount: number
successCount: number
} | null>(null)
const hasCalledOnPreviewLoaded = useRef(false)
// Selection handlers
const handleRowSelect = (rowIndex: number) => {
setSelectedRows((prev) => {
const newSelection = prev.includes(rowIndex)
? prev.filter((index) => index !== rowIndex)
: [...prev, rowIndex]
return newSelection
})
}
const handleSelectAll = () => {
if (selectAll) {
// Deselect all
setSelectedRows([])
} else {
// Select all available rows
const allRowIndices = previewData?.rows
? Array.from({ length: previewData.rows.length }, (_, i) => i)
: []
setSelectedRows(allRowIndices)
}
}
// Update selectAll state when individual rows are selected
useEffect(() => {
if (previewData?.rows && previewData.rows.length > 0) {
const allSelected = selectedRows.length === previewData.rows.length
setSelectAll(allSelected)
} else {
setSelectAll(false)
}
}, [selectedRows, previewData?.rows])
useEffect(() => {
const loadData = async () => {
try {
const data = await importService.getImportPreview(session.id, gridDto)
setPreviewData(data)
// Reset selections when new data is loaded
setSelectedRows([])
setSelectAll(false)
// Reset success message when new data is loaded
setShowSuccessMessage(false)
setLastExecutionResult(null)
// Call onPreviewLoaded callback to refresh history (only once per session)
if (onPreviewLoaded && !hasCalledOnPreviewLoaded.current) {
hasCalledOnPreviewLoaded.current = true
onPreviewLoaded()
}
} catch (error) {
console.error('Failed to load preview data:', error)
}
}
// Reset the flag when session changes
hasCalledOnPreviewLoaded.current = false
loadData()
}, [session.id, importService, gridDto]) // Remove session.status, onSessionUpdate and onPreviewLoaded from dependencies
const getStatusColor = (status: string) => {
switch (status) {
case 'uploaded':
return 'text-green-600 bg-green-50 border-green-200'
case 'failed':
return 'text-red-600 bg-red-50 border-red-200'
case 'validating':
return 'text-yellow-600 bg-yellow-50 border-yellow-200'
default:
return 'text-blue-600 bg-blue-50 border-blue-200'
}
}
const canExecute = (previewData?.rows?.length || 0) > 0 && selectedRows.length > 0 && !loading
// Execute handler to clear selections after execution
const handleExecute = async () => {
const selectedCount = selectedRows.length
try {
const result = await onExecute(session.id, session.listFormCode, selectedRows)
// Check if execution was successful
if (result && typeof result === 'object' && 'successCount' in result) {
const successCount = result.successCount || 0
setLastExecutionResult({ selectedCount, successCount })
// Show success message if all selected rows were processed successfully
if (successCount === selectedCount) {
setShowSuccessMessage(true)
// Hide success message after 5 seconds
setTimeout(() => {
setShowSuccessMessage(false)
}, 5000)
}
}
// Clear selections after execute
setSelectedRows([])
setSelectAll(false)
} catch (error) {
console.error('Import execution failed:', error)
// Clear selections even on error
setSelectedRows([])
setSelectAll(false)
}
}
return (
<div className="bg-white rounded-xl shadow-sm border border-slate-200">
{/* Header */}
<div className="p-3 border-b border-slate-200">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-center gap-4">
{/* Başlık kısmı - Üstte mobile, solda desktop */}
<div className="flex items-center order-1 lg:order-none">
<Eye className="w-5 h-5 mr-2 text-blue-500" />
<h3 className="text-xl font-semibold text-slate-800">Import Preview</h3>
</div>
{/* İstatistik kartları - Mobile'da alt alta, desktop'ta yan yana */}
<div className="order-3 lg:order-none lg:absolute lg:left-1/2 lg:transform lg:-translate-x-1/2">
<div className="flex flex-col sm:flex-row justify-center gap-2">
<div className="text-center px-3 py-1 bg-blue-50 rounded-full border border-blue-200 font-bold text-blue-600">
{previewData?.rows?.length || session.totalRows || 0}{' '}
<span className="text-xs text-blue-700">Total Rows</span>
</div>
</div>
</div>
{/* Durum göstergesi - Altta mobile, sağda desktop */}
<div className="flex justify-center lg:justify-end items-center order-2 lg:order-none lg:ml-auto w-full lg:w-auto">
<span
className={`px-3 py-1 rounded-full text-sm font-medium border w-full lg:w-auto text-center ${getStatusColor(
session.status,
)}`}
>
{session.status.charAt(0).toUpperCase() + session.status.slice(1)}
</span>
</div>
</div>
</div>
{/* Preview Data */}
{previewData && previewData.headers && previewData.headers.length > 0 ? (
<div className="p-3 border-b border-slate-200">
<h4 className="font-semibold text-slate-800 mb-4">Data Preview</h4>
<div className="overflow-auto border border-slate-200 rounded-lg max-h-90">
<table className="w-full text-sm min-w-full">
<thead className="bg-slate-50 sticky top-0 z-10">
<tr>
<th className="px-4 py-2 text-left font-medium text-slate-700 whitespace-nowrap w-12">
<input
type="checkbox"
checked={selectAll}
onChange={handleSelectAll}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
</th>
{previewData.headers.map((header: string, index: number) => (
<th
key={index}
className="px-4 py-2 text-left font-medium text-slate-700 whitespace-nowrap"
>
{header}
</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{previewData.rows.map((row: any[], rowIndex: number) => (
<tr
key={rowIndex}
className={`hover:bg-slate-50 ${
selectedRows.includes(rowIndex) ? 'bg-blue-50' : ''
}`}
>
<td className="px-4 py-2">
<input
type="checkbox"
checked={selectedRows.includes(rowIndex)}
onChange={() => handleRowSelect(rowIndex)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
</td>
{row.map((cell, cellIndex) => (
<td
key={cellIndex}
className="px-4 py-2 text-slate-600 whitespace-nowrap max-w-xs truncate"
>
{cell?.toString() || '-'}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
) : previewData && previewData.headers && previewData.headers.length === 0 ? (
<div className="p-6 border-b border-slate-200">
<div className="text-center py-8">
<AlertTriangle className="w-12 h-12 mx-auto mb-4 text-yellow-500" />
<h4 className="font-semibold text-slate-800 mb-2">No Data Found</h4>
<p className="text-slate-600">
Unable to parse data from the uploaded file. Please check the file format and content.
</p>
</div>
</div>
) : (
<div className="p-6 border-b border-slate-200">
<div className="text-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"></div>
<h4 className="font-semibold text-slate-800 mb-2">Loading Preview...</h4>
<p className="text-slate-600">Analyzing uploaded file content...</p>
</div>
</div>
)}
{/* Actions */}
<div className="p-3">
{/* Success Message */}
{showSuccessMessage && lastExecutionResult && (
<div className="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg">
<div className="flex items-center space-x-2 text-green-700">
<CheckCircle className="w-5 h-5" />
<span className="font-medium">
Import completed successfully! {lastExecutionResult.successCount} of{' '}
{lastExecutionResult.selectedCount} rows were imported.
</span>
</div>
</div>
)}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
{selectedRows.length === 0 && (previewData?.rows?.length || 0) > 0 && (
<div className="flex items-center space-x-2 text-orange-600">
<AlertTriangle className="w-5 h-5" />
<span className="font-medium">Please select rows to import using checkboxes</span>
</div>
)}
{selectedRows.length > 0 && (
<div className="flex items-center space-x-2 text-blue-600">
<CheckCircle className="w-5 h-5" />
<span className="font-medium">{selectedRows.length} rows selected for import</span>
</div>
)}
{(previewData?.rows?.length || 0) === 0 && (
<div className="flex items-center space-x-2 text-red-600">
<AlertTriangle className="w-5 h-5" />
<span className="font-medium">No data available for import</span>
</div>
)}
</div>
<div className="flex space-x-3">
<button className="px-4 py-2 text-slate-600 hover:text-slate-800 hover:bg-slate-100 rounded-lg transition-colors flex items-center space-x-2">
<X className="w-4 h-4" />
<span>Cancel</span>
</button>
<button
onClick={handleExecute}
disabled={!canExecute || loading}
className="px-6 py-2 bg-blue-500 hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors flex items-center space-x-2"
>
{loading ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Processing...</span>
</>
) : (
<>
<Play className="w-4 h-4" />
<span>Execute Import</span>
</>
)}
</button>
</div>
</div>
</div>
</div>
)
}