Usman Bari
Initial commit
37a6ee1
Raw
History Blame Contribute Delete
12.4 kB
import React, { useRef, useState } from "react";
import { uploadCSV, deleteDataset, deleteSchemaDataset } from "../api";
export default function Sidebar({
datasets,
schemaDatasets = [],
selectedDataset,
onSelect,
onUploadSuccess,
onUploadSchemaClick,
onDelete,
history,
onRunQuestion,
addToast
}) {
const fileInputRef = useRef(null);
const [isUploading, setIsUploading] = useState(false);
const handleUploadClick = () => {
if (fileInputRef.current) {
fileInputRef.current.value = ""; // Reset file input
fileInputRef.current.click();
}
};
const handleFileChange = async (e) => {
const file = e.target.files[0];
if (!file) return;
if (!file.name.endsWith(".csv")) {
addToast("Only .csv files are allowed.", "error");
return;
}
setIsUploading(true);
try {
console.log("[Sidebar] Uploading CSV file:", file.name);
const result = await uploadCSV(file);
setIsUploading(false);
if (onUploadSuccess) {
onUploadSuccess(result);
}
} catch (err) {
setIsUploading(false);
addToast("Upload failed: " + err.message, "error");
}
};
const handleDeleteClick = async (e, dbName, mode = "csv") => {
e.stopPropagation(); // Avoid selecting the dataset card when deleting
if (window.confirm(`Are you sure you want to delete the dataset '${dbName}'?`)) {
try {
console.log(`[Sidebar] Deleting ${mode} dataset:`, dbName);
if (mode === "schema") {
await deleteSchemaDataset(dbName);
} else {
await deleteDataset(dbName);
}
if (onDelete) {
onDelete(dbName, mode);
}
} catch (err) {
addToast("Delete failed: " + err.message, "error");
}
}
};
// Separate sample vs user datasets
const sampleDatasets = datasets.filter((ds) => ds.is_sample);
const userDatasets = datasets.filter((ds) => !ds.is_sample);
return (
<aside className="sidebar">
<div className="sidebar-header">
<h1 className="sidebar-logo">
🔍 NL Analyst<span>.</span>
</h1>
</div>
<div className="sidebar-content">
{/* Hidden File Input */}
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
accept=".csv"
style={{ display: "none" }}
/>
{/* ① "🔗 Relational Databases" section (purple #8b5cf6) — AT THE TOP */}
<div>
<div className="divider-label" style={{ color: "#8b5cf6", display: "flex", alignItems: "center", gap: "6px" }}>
<span>🔗</span> Relational Databases
</div>
<button
className="btn-primary"
onClick={onUploadSchemaClick}
style={{
width: "100%",
justifyContent: "center",
backgroundColor: "#8b5cf6",
marginBottom: "12px",
fontSize: "0.8rem",
padding: "8px 12px"
}}
onMouseEnter={(e) => e.target.style.backgroundColor = "#7c3aed"}
onMouseLeave={(e) => e.target.style.backgroundColor = "#8b5cf6"}
>
+ Upload Schema DB
</button>
{schemaDatasets && schemaDatasets.length > 0 ? (
<div className="dataset-list">
{schemaDatasets.map((ds) => {
const isSelected = selectedDataset && selectedDataset.db_name === ds.db_name && selectedDataset.mode === "schema";
return (
<button
key={ds.db_name}
className={`dataset-item ${isSelected ? "selected" : ""}`}
onClick={() => onSelect({ ...ds, mode: "schema" })}
style={isSelected ? { borderLeftColor: "#8b5cf6", backgroundColor: "rgba(139, 92, 246, 0.1)" } : {}}
>
<div className="dataset-info-block">
<div className="dataset-title-row">
<span className="dataset-name">{ds.display_name}</span>
<span
className="badge"
style={{
backgroundColor: "rgba(139, 92, 246, 0.15)",
color: "#a78bfa",
border: "1px solid rgba(139, 92, 246, 0.3)"
}}
>
RELATIONAL
</span>
</div>
<span className="dataset-meta">
{ds.total_tables} tables • {ds.relationships ? ds.relationships.length : 0} relationships
</span>
</div>
{!ds.is_sample && (
<button
className="btn-delete-dataset"
onClick={(e) => handleDeleteClick(e, ds.db_name, "schema")}
title="Delete relational dataset"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
)}
</button>
);
})}
</div>
) : (
<div style={{ fontSize: "0.75rem", color: "var(--text-muted)", textAlign: "center", padding: "10px", border: "1px dashed var(--border-color)", borderRadius: "6px", marginBottom: "16px" }}>
Upload a .sql schema file to query relational data
</div>
)}
</div>
{/* ② Divider line */}
<hr style={{ border: "none", borderTop: "1px solid var(--border-color)", margin: "16px 0" }} />
{/* ③ "📊 CSV Datasets" section (blue #3b82f6) — IN THE MIDDLE */}
<div>
<div className="divider-label" style={{ color: "#3b82f6", display: "flex", alignItems: "center", gap: "6px" }}>
<span>📊</span> CSV Datasets
</div>
<button
className="btn-primary"
onClick={handleUploadClick}
disabled={isUploading}
style={{
width: "100%",
justifyContent: "center",
backgroundColor: "#3b82f6",
marginBottom: "12px"
}}
onMouseEnter={(e) => e.target.style.backgroundColor = "#2563eb"}
onMouseLeave={(e) => e.target.style.backgroundColor = "#3b82f6"}
>
{isUploading ? "Uploading..." : "+ Upload CSV"}
</button>
<div className="dataset-list">
{sampleDatasets.map((ds) => {
const isSelected = selectedDataset && selectedDataset.db_name === ds.db_name && selectedDataset.mode !== "schema";
return (
<button
key={ds.db_name}
className={`dataset-item ${isSelected ? "selected" : ""}`}
onClick={() => onSelect(ds)}
>
<div className="dataset-info-block">
<div className="dataset-title-row">
<span className="dataset-name">{ds.display_name}</span>
<span className="badge badge-sample">Sample</span>
</div>
<span className="dataset-meta">
{ds.row_count} rows • {ds.columns.length} columns
</span>
</div>
</button>
);
})}
{userDatasets.map((ds) => {
const isSelected = selectedDataset && selectedDataset.db_name === ds.db_name && selectedDataset.mode !== "schema";
return (
<button
key={ds.db_name}
className={`dataset-item ${isSelected ? "selected" : ""}`}
onClick={() => onSelect(ds)}
>
<div className="dataset-info-block">
<div className="dataset-title-row">
<span className="dataset-name">{ds.display_name}</span>
<span className="badge badge-user">User</span>
</div>
<span className="dataset-meta">
{ds.row_count} rows • {ds.columns.length} columns
</span>
</div>
<button
className="btn-delete-dataset"
onClick={(e) => handleDeleteClick(e, ds.db_name)}
title="Delete dataset"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</button>
);
})}
</div>
</div>
{/* Recent Queries Section */}
{selectedDataset && history && history.length > 0 && (
<div style={{ marginTop: "16px", borderTop: "1px solid var(--border-color)", paddingTop: "16px" }}>
<div className="divider-label">Recent Queries</div>
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
{history.slice(0, 5).map((item, idx) => (
<button
key={idx}
className="dataset-item"
onClick={() => onRunQuestion && onRunQuestion(item.question)}
style={{
padding: "8px 12px",
border: "1px solid var(--border-color)",
backgroundColor: "var(--surface-elevated)",
borderRadius: "6px"
}}
title={item.question}
>
<div style={{ display: "flex", alignItems: "center", gap: "8px", overflow: "hidden", width: "100%" }}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="var(--text-muted)"
strokeWidth="2.5"
style={{ flexShrink: 0 }}
>
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
<span
style={{
fontSize: "0.8rem",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
color: "var(--text-secondary)"
}}
>
{item.question}
</span>
</div>
</button>
))}
</div>
</div>
)}
</div>
</aside>
);
}