Khushal-kreeda
fix: ui enhancements
826a975
import React, { useState, useEffect, useCallback } from "react";
import { debugLog } from "../utils/config";
import TroubleshootingGuide from "./TroubleshootingGuide";
import "./DataViewer.css";
const DataViewer = ({ s3Url, onDownload, showPreviewOnly = false }) => {
const [data, setData] = useState([]);
const [columns, setColumns] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [rowsPerPage] = useState(10);
const fetchData = useCallback(async () => {
try {
setLoading(true);
setError(null);
debugLog("Fetching data from S3 URL:", s3Url);
// Try multiple approaches to fetch the data
let response;
// Method 1: Direct fetch with CORS
try {
response = await fetch(s3Url, {
method: "GET",
headers: {
Accept: "text/csv,text/plain,application/octet-stream,*/*",
},
mode: "cors",
});
} catch (corsError) {
debugLog("CORS fetch failed, trying no-cors:", corsError);
// Method 2: Try with no-cors mode (limited but might work)
try {
response = await fetch(s3Url, {
method: "GET",
mode: "no-cors",
});
} catch (noCorsError) {
debugLog("No-cors fetch also failed:", noCorsError);
throw new Error(
"Unable to preview data due to CORS restrictions. You can still download the file directly."
);
}
}
if (!response.ok && response.status !== 0) {
// If direct fetch fails, provide helpful error messages
if (response.status === 403 || response.status === 401) {
throw new Error(
"Access denied. The file may require authentication or have expired."
);
} else if (response.status === 404) {
throw new Error(
"File not found. The download link may have expired."
);
} else {
throw new Error(
`Unable to fetch data (${response.status}). You can still download the file directly.`
);
}
}
// For no-cors responses, we can't read the content
if (response.type === "opaque") {
throw new Error(
"Preview not available due to CORS restrictions. Please download the file to view the data."
);
}
const csvText = await response.text();
if (!csvText || csvText.trim().length === 0) {
throw new Error("The downloaded file appears to be empty");
}
const parsedData = parseCSV(csvText);
if (parsedData.length > 0) {
setColumns(Object.keys(parsedData[0]));
setData(parsedData);
debugLog("Data parsed successfully:", {
rows: parsedData.length,
columns: Object.keys(parsedData[0]).length,
sampleData: parsedData.slice(0, 2),
});
} else {
throw new Error("No valid data rows found in the file");
}
} catch (err) {
setError(err.message);
debugLog("Error fetching data:", err);
} finally {
setLoading(false);
}
}, [s3Url]);
useEffect(() => {
if (s3Url) {
fetchData();
}
}, [s3Url, fetchData]);
const parseCSV = (csvText) => {
try {
const lines = csvText.trim().split("\n");
if (lines.length < 2) return [];
// Handle different CSV formats and potential quotes
const parseCSVLine = (line) => {
const result = [];
let current = "";
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
inQuotes = !inQuotes;
} else if (char === "," && !inQuotes) {
result.push(current.trim());
current = "";
} else {
current += char;
}
}
result.push(current.trim());
return result.map((value) => value.replace(/^"(.*)"$/, "$1")); // Remove outer quotes
};
const headers = parseCSVLine(lines[0]);
const rows = [];
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === "") continue; // Skip empty lines
const values = parseCSVLine(lines[i]);
if (values.length > 0 && values.some((val) => val.trim() !== "")) {
const row = {};
headers.forEach((header, index) => {
row[header] = values[index] || "";
});
rows.push(row);
}
}
return rows;
} catch (err) {
debugLog("Error parsing CSV:", err);
throw new Error(
"Failed to parse CSV data. The file format may be invalid."
);
}
};
const getPaginatedData = () => {
const startIndex = (currentPage - 1) * rowsPerPage;
const endIndex = startIndex + rowsPerPage;
return data.slice(startIndex, endIndex);
};
const totalPages = Math.ceil(data.length / rowsPerPage);
const handleDownload = () => {
if (onDownload) {
onDownload();
} else {
window.open(s3Url, "_blank");
}
};
if (loading) {
return (
<div className="data-viewer loading">
<div className="spinner"></div>
<p>Loading data preview...</p>
</div>
);
}
if (error) {
return (
<div className="data-viewer error">
<div className="status-message error">
<div className="status-message-icon">❌</div>
<div className="status-message-content">
<h4>Unable to Preview Data</h4>
<p>{error}</p>
<div
className="error-help"
style={{ marginTop: "0.75rem", fontSize: "0.875rem" }}
>
<strong>
Don't worry! Your data has been generated successfully.
</strong>
<br />
<strong>Possible solutions:</strong>
<ul
style={{
marginTop: "0.5rem",
paddingLeft: "1.5rem",
textAlign: "left",
}}
>
{!showPreviewOnly && (
<li>
<strong>Download the file directly</strong> using the button
below
</li>
)}
<li>
The preview may fail due to browser security restrictions
</li>
<li>
Your generated data is still available and ready
{showPreviewOnly ? "" : " to download"}
</li>
</ul>
</div>
</div>
</div>
<div
className="error-actions"
style={{ marginTop: "1.5rem", textAlign: "center" }}
>
{!showPreviewOnly && (
<button
className="btn btn-primary btn-large"
onClick={handleDownload}
style={{
marginRight: "0.75rem",
padding: "12px 24px",
fontSize: "1rem",
}}
>
πŸ“₯ Download Generated Data
</button>
)}
<button
className="btn btn-secondary"
onClick={() => fetchData()}
style={{ marginLeft: showPreviewOnly ? "0" : "0.75rem" }}
>
πŸ”„ Try Preview Again
</button>
</div>
<div
className="success-note"
style={{
marginTop: "1.5rem",
padding: "1rem",
background: "var(--success-light, #e8f5e8)",
borderRadius: "8px",
border: "1px solid var(--success, #28a745)",
textAlign: "center",
}}
>
<div
style={{
color: "var(--success, #28a745)",
fontWeight: "bold",
marginBottom: "0.5rem",
}}
>
βœ… Data Generation Completed Successfully!
</div>
<div style={{ fontSize: "0.875rem", color: "var(--text-secondary)" }}>
The preview failed, but your synthetic data has been generated and
is ready for download.
</div>
</div>
<TroubleshootingGuide
generatedDataLink={s3Url}
onDownload={handleDownload}
/>
</div>
);
}
if (data.length === 0) {
return (
<div className="data-viewer empty">
<div className="status-message warning">
<div className="status-message-icon">⚠️</div>
<div className="status-message-content">
<h4>No Data Available</h4>
<p>The generated file appears to be empty.</p>
</div>
</div>
{!showPreviewOnly && (
<div className="download-section" style={{ marginTop: "1rem" }}>
<button className="btn btn-primary" onClick={handleDownload}>
πŸ“₯ Download File
</button>
</div>
)}
</div>
);
}
return (
<div className="data-viewer">
<div className="data-viewer-header">
<div className="data-info">
<h4>πŸ“Š Generated Data {showPreviewOnly ? "Preview" : ""}</h4>
<p>
Showing {getPaginatedData().length} of {data.length} rows β€’{" "}
{columns.length} columns
</p>
</div>
{!showPreviewOnly && (
<button
className="btn btn-success download-btn"
onClick={handleDownload}
>
πŸ“₯ Download Complete File
</button>
)}
</div>
<div className="data-table-container">
<table className="data-table">
<thead>
<tr>
{columns.map((column, index) => (
<th key={index} title={column}>
{column}
</th>
))}
</tr>
</thead>
<tbody>
{getPaginatedData().map((row, index) => (
<tr key={index}>
{columns.map((column, colIndex) => (
<td key={colIndex} title={row[column]}>
{row[column]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
{totalPages > 1 && (
<div className="pagination">
<button
className="btn btn-secondary"
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
disabled={currentPage === 1}
>
← Previous
</button>
<span className="pagination-info">
Page {currentPage} of {totalPages}
</span>
<button
className="btn btn-secondary"
onClick={() =>
setCurrentPage((prev) => Math.min(prev + 1, totalPages))
}
disabled={currentPage === totalPages}
>
Next β†’
</button>
</div>
)}
{showPreviewOnly && data.length > 0 && (
<div
className="preview-note"
style={{
marginTop: "1rem",
padding: "0.75rem",
background: "var(--bg-tertiary)",
borderRadius: "8px",
fontSize: "0.875rem",
color: "var(--text-secondary)",
textAlign: "center",
}}
>
πŸ’‘ Showing first {Math.min(data.length, rowsPerPage * totalPages)}{" "}
rows. Download the complete file to view all {data.length} rows.
</div>
)}
</div>
);
};
export default DataViewer;