Sid-WC121
bugfix3
0719363
Raw History Blame Contribute Delete
9.63 kB
import React from 'react';
import { useUIStore, type SidebarTab } from '../store/ui';
import { useSessionStore } from '../store/session';
import { SchemaView } from './SchemaView';
import { ManipulationPanel } from './ManipulationPanel';
import { VisualizationPanel } from './VisualizationPanel';
import { useExport, useExportNotebook } from '../api/hooks';
const TABS: { key: SidebarTab; label: string; icon: React.ReactNode }[] = [
{
key: 'data',
label: 'Data',
icon: (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M3 14h18" />
</svg>
),
},
{
key: 'transform',
label: 'Transform',
icon: (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
),
},
{
key: 'visualize',
label: 'Visualize',
icon: (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6m6 0V9a2 2 0 012-2h2a2 2 0 012 2v10m6 0v-4a2 2 0 00-2-2h-2a2 2 0 00-2 2v4" />
</svg>
),
},
{
key: 'export',
label: 'Export',
icon: (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
),
},
];
function ExportTab() {
const { download: downloadCSV, loading: csvLoading } = useExport();
const { download: downloadNotebook, loading: nbLoading } = useExportNotebook();
const { columns, rowCount, tableName } = useSessionStore();
return (
<div className="p-4 space-y-4">
{/* Jupyter Notebook Export - PROMINENT */}
<div>
<h4 className="text-sm font-semibold text-gray-900 mb-3">Export</h4>
<div className="bg-gradient-to-br from-brand-50 to-brand-100/50 border border-brand-200 rounded-xl p-4">
<div className="flex items-center gap-2.5 mb-2">
<div className="w-8 h-8 bg-brand-600 rounded-lg flex items-center justify-center flex-shrink-0">
<svg className="w-4 h-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
</svg>
</div>
<div>
<span className="text-sm font-semibold text-gray-900">Jupyter Notebook</span>
<span className="ml-2 text-[10px] bg-brand-200 text-brand-800 px-1.5 py-0.5 rounded-full font-medium">Recommended</span>
</div>
</div>
<p className="text-xs text-gray-600 mb-3 leading-relaxed">
Export as <span className="font-mono bg-white/60 px-1 rounded">.ipynb</span> with clean Python code — only pandas and the relevant operations, ready to run.
</p>
<button
onClick={() => downloadNotebook()}
disabled={nbLoading}
className="w-full px-4 py-2.5 bg-brand-600 text-white rounded-lg hover:bg-brand-700 transition-colors font-medium text-sm shadow-sm disabled:opacity-50"
>
{nbLoading ? (
<span className="flex items-center justify-center gap-2">
<span className="animate-spin w-4 h-4 border-2 border-white border-t-transparent rounded-full" />
Generating...
</span>
) : (
<span className="flex items-center justify-center gap-2">
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
Export Notebook (.ipynb)
</span>
)}
</button>
</div>
</div>
{/* CSV Export */}
<div>
<div className="bg-gray-50 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1">
<svg className="w-4 h-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<span className="text-sm font-medium text-gray-700">CSV File</span>
</div>
<p className="text-xs text-gray-500 mb-2">
Raw data export ({rowCount.toLocaleString()} rows, {columns.length} columns)
</p>
<button
onClick={() => downloadCSV()}
disabled={csvLoading}
className="btn-secondary w-full text-xs"
>
{csvLoading ? 'Exporting...' : 'Export CSV'}
</button>
</div>
</div>
{/* Data Summary */}
<div>
<h4 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Summary</h4>
<div className="bg-gray-50 rounded-lg p-3 space-y-1.5 text-xs text-gray-600">
<div className="flex justify-between">
<span>Table</span>
<span className="font-medium text-gray-900 truncate ml-2 max-w-[120px]" title={tableName || '—'}>{tableName || '—'}</span>
</div>
<div className="flex justify-between">
<span>Rows</span>
<span className="font-medium text-gray-900">{rowCount.toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span>Columns</span>
<span className="font-medium text-gray-900">{columns.length}</span>
</div>
<div className="flex justify-between">
<span>Numeric</span>
<span className="font-medium text-gray-900">{columns.filter(c => c.column_type === 'numeric').length}</span>
</div>
<div className="flex justify-between">
<span>Categorical</span>
<span className="font-medium text-gray-900">{columns.filter(c => c.column_type === 'categorical').length}</span>
</div>
<div className="flex justify-between">
<span>Datetime</span>
<span className="font-medium text-gray-900">{columns.filter(c => c.column_type === 'datetime').length}</span>
</div>
</div>
</div>
</div>
);
}
export function Sidebar() {
const { activeSidebarTab, setActiveSidebarTab } = useUIStore();
const { isLoaded } = useSessionStore();
if (!isLoaded) return null;
return (
<div className="w-80 flex-shrink-0 bg-white border-r border-gray-200 flex flex-col h-full">
{/* Tab bar */}
<div className="flex border-b border-gray-200 flex-shrink-0">
{TABS.map((tab) => (
<button
key={tab.key}
onClick={() => setActiveSidebarTab(tab.key)}
className={`sidebar-tab flex flex-col items-center gap-0.5 py-2.5 ${
activeSidebarTab === tab.key ? 'sidebar-tab-active' : 'sidebar-tab-inactive'
}`}
title={tab.label}
>
{tab.icon}
<span className="text-[10px]">{tab.label}</span>
</button>
))}
</div>
{/* Tab content */}
<div className="flex-1 sidebar-scroll">
{activeSidebarTab === 'data' && <DataTab />}
{activeSidebarTab === 'transform' && <ManipulationPanel />}
{activeSidebarTab === 'visualize' && <VisualizationPanel />}
{activeSidebarTab === 'export' && <ExportTab />}
</div>
</div>
);
}
function DataTab() {
const { columns, rowCount } = useSessionStore();
const numericCount = columns.filter(c => c.column_type === 'numeric').length;
const categoricalCount = columns.filter(c => c.column_type === 'categorical').length;
const datetimeCount = columns.filter(c => c.column_type === 'datetime').length;
return (
<div className="p-4">
<div className="flex items-center justify-between mb-3">
<h4 className="text-sm font-semibold text-gray-900">Schema</h4>
<span className="text-xs text-gray-500">{rowCount.toLocaleString()} rows</span>
</div>
{/* Column type summary */}
<div className="flex gap-2 mb-3 flex-wrap">
{numericCount > 0 && (
<span className="inline-flex items-center gap-1 text-[10px] px-2 py-0.5 bg-blue-50 text-blue-700 rounded-full">
<span className="font-bold">#</span> {numericCount} numeric
</span>
)}
{categoricalCount > 0 && (
<span className="inline-flex items-center gap-1 text-[10px] px-2 py-0.5 bg-amber-50 text-amber-700 rounded-full">
<span className="font-bold">Aa</span> {categoricalCount} categorical
</span>
)}
{datetimeCount > 0 && (
<span className="inline-flex items-center gap-1 text-[10px] px-2 py-0.5 bg-green-50 text-green-700 rounded-full">
<span className="font-bold">DT</span> {datetimeCount} datetime
</span>
)}
</div>
<SchemaView compact />
</div>
);
}