snp-sync-batch/frontend/src/components/SyncDataPreviewModal.tsx

109 lines
3.9 KiB
TypeScript

import { useState, useEffect } from 'react';
import { batchApi, type SyncDataPreviewResponse } from '../api/batchApi';
import LoadingSpinner from './LoadingSpinner';
interface Props {
open: boolean;
tableKey: string;
tableName: string;
onClose: () => void;
}
export default function SyncDataPreviewModal({ open, tableKey, tableName, onClose }: Props) {
const [data, setData] = useState<SyncDataPreviewResponse | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!open || !tableKey) return;
setLoading(true);
setError(null);
batchApi.getSyncDataPreview(tableKey, 10)
.then(setData)
.catch((e) => setError(e.message))
.finally(() => setLoading(false));
}, [open, tableKey]);
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-wing-overlay" onClick={onClose}>
<div
className="bg-wing-surface rounded-xl shadow-2xl max-w-5xl w-full mx-4 max-h-[80vh] flex flex-col"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-wing-border">
<div>
<h3 className="text-lg font-semibold text-wing-text">{tableName}</h3>
<p className="text-xs text-wing-muted mt-0.5">
{data ? `${data.targetSchema}.${data.targetTable} | 총 ${data.totalCount.toLocaleString()}` : ''}
</p>
</div>
<button
onClick={onClose}
className="px-3 py-1.5 text-sm text-wing-muted hover:text-wing-text transition-colors"
>
</button>
</div>
{/* Body */}
<div className="flex-1 overflow-auto p-4">
{loading && <LoadingSpinner />}
{error && (
<div className="text-center py-8 text-wing-muted">
<p className="text-red-400"> : {error}</p>
</div>
)}
{!loading && !error && data && data.rows.length === 0 && (
<div className="text-center py-8 text-wing-muted"> </div>
)}
{!loading && !error && data && data.rows.length > 0 && (
<div className="overflow-x-auto">
<table className="w-full text-xs">
<thead>
<tr className="border-b border-wing-border">
{data.columns.map((col) => (
<th
key={col}
className="px-3 py-2 text-left font-medium text-wing-muted whitespace-nowrap bg-wing-card"
>
{col}
</th>
))}
</tr>
</thead>
<tbody>
{data.rows.map((row, idx) => (
<tr key={idx} className="border-b border-wing-border/50 hover:bg-wing-hover">
{data.columns.map((col) => (
<td key={col} className="px-3 py-1.5 text-wing-text whitespace-nowrap max-w-[200px] truncate">
{formatCellValue(row[col])}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
{/* Footer */}
{data && data.rows.length > 0 && (
<div className="px-6 py-3 border-t border-wing-border text-xs text-wing-muted">
{data.rows.length} ( {data.totalCount.toLocaleString()})
</div>
)}
</div>
</div>
);
}
function formatCellValue(value: unknown): string {
if (value === null || value === undefined) return '-';
if (typeof value === 'object') return JSON.stringify(value);
return String(value);
}