sozsoft-platform/ui/src/components/importManager/ImportPreview.tsx
Sedat Öztürk 429227df1d Initial
2026-02-24 23:44:16 +03:00

344 lines
13 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 { FaCheckCircle, FaExclamationTriangle, FaEye, FaPlay, FaTimes } from 'react-icons/fa'
import { ListFormImportDto } from '@/proxy/imports/models'
import { GridDto } from '@/proxy/form/models'
import { ImportService } from '@/services/import.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
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)
const { translate } = useLocalization()
// 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">
<FaEye className="w-5 h-5 mr-2 text-blue-500" />
<h3 className="text-xl font-semibold text-slate-800">
{translate('::App.Listforms.ImportManager.ImportPreviewTitle')}
</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">
{translate('::App.Listforms.ImportManager.TotalRows')}
</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">
{translate('::App.Listforms.ImportManager.DataPreviewTitle')}
</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">
<FaExclamationTriangle className="w-12 h-12 mx-auto mb-4 text-yellow-500" />
<h4 className="font-semibold text-slate-800 mb-2">
{translate('::App.Listforms.ImportManager.NoDataFoundTitle')}
</h4>
<p className="text-slate-600">
{translate('::App.Listforms.ImportManager.NoDataFoundDescription')}
</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">
{translate('::App.Listforms.ImportManager.LoadingPreviewTitle')}
</h4>
<p className="text-slate-600">
{translate('::App.Listforms.ImportManager.LoadingPreviewDescription')}
</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">
<FaCheckCircle className="w-5 h-5" />
<span className="font-medium">
{translate('::App.Listforms.ImportManager.ImportProgress.Status.Uploaded')}{' '}
{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">
<FaExclamationTriangle className="w-5 h-5" />
<span className="font-medium">
{translate('::App.Listforms.ImportManager.SelectRowsWarning')}
</span>
</div>
)}
{selectedRows.length > 0 && (
<div className="flex items-center space-x-2 text-blue-600">
<FaCheckCircle className="w-5 h-5" />
<span className="font-medium">
{selectedRows.length} {translate('::App.Listforms.ImportManager.RowsSelected')}
</span>
</div>
)}
{(previewData?.rows?.length || 0) === 0 && (
<div className="flex items-center space-x-2 text-red-600">
<FaExclamationTriangle className="w-5 h-5" />
<span className="font-medium">
{translate('::App.Listforms.ImportManager.NoRowsAvailable')}
</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">
<FaTimes className="w-4 h-4" />
<span>{translate('::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>{translate('::App.Listforms.Status.Processing')}</span>
</>
) : (
<>
<FaPlay className="w-4 h-4" />
<span>{translate('::App.Listforms.ImportManager.Button.ExecuteImport')}</span>
</>
)}
</button>
</div>
</div>
</div>
</div>
)
}