npcts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/dist/adapters/base.d.ts +13 -0
- package/dist/adapters/base.js +1 -0
- package/dist/adapters/electron/bridge.d.ts +4 -0
- package/dist/adapters/electron/bridge.js +88 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.js +3 -0
- package/dist/core/browser.d.ts +25 -0
- package/dist/core/browser.js +1 -0
- package/dist/core/chat.d.ts +41 -0
- package/dist/core/chat.js +1 -0
- package/dist/core/database.d.ts +56 -0
- package/dist/core/database.js +50 -0
- package/dist/core/files.d.ts +24 -0
- package/dist/core/files.js +15 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +10 -0
- package/dist/core/jobs.d.ts +20 -0
- package/dist/core/jobs.js +1 -0
- package/dist/core/layout.d.ts +30 -0
- package/dist/core/layout.js +41 -0
- package/dist/core/stream.d.ts +10 -0
- package/dist/core/stream.js +1 -0
- package/dist/core/types.d.ts +23 -0
- package/dist/core/types.js +1 -0
- package/dist/core/utils.d.ts +19 -0
- package/dist/core/utils.js +19 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +29 -0
- package/dist/ui/chat/components/ChatHeaderBar.d.ts +13 -0
- package/dist/ui/chat/components/ChatHeaderBar.js +32 -0
- package/dist/ui/chat/components/ChatInterface.d.ts +10 -0
- package/dist/ui/chat/components/ChatInterface.js +19 -0
- package/dist/ui/chat/components/ChatMessage.d.ts +7 -0
- package/dist/ui/chat/components/ChatMessage.js +10 -0
- package/dist/ui/chat/components/ChatPane.d.ts +20 -0
- package/dist/ui/chat/components/ChatPane.js +20 -0
- package/dist/ui/chat/components/ChatView.d.ts +10 -0
- package/dist/ui/chat/components/ChatView.js +36 -0
- package/dist/ui/chat/components/ConversationList.d.ts +28 -0
- package/dist/ui/chat/components/ConversationList.js +113 -0
- package/dist/ui/chat/components/InPaneSearchBar.d.ts +12 -0
- package/dist/ui/chat/components/InPaneSearchBar.js +44 -0
- package/dist/ui/chat/components/InputArea.d.ts +7 -0
- package/dist/ui/chat/components/InputArea.js +46 -0
- package/dist/ui/chat/components/MessageAttachments.d.ts +5 -0
- package/dist/ui/chat/components/MessageAttachments.js +6 -0
- package/dist/ui/chat/components/MessageItem.d.ts +28 -0
- package/dist/ui/chat/components/MessageItem.js +43 -0
- package/dist/ui/chat/components/PredictiveTextOverlay.d.ts +10 -0
- package/dist/ui/chat/components/PredictiveTextOverlay.js +75 -0
- package/dist/ui/chat/context/ChatContext.d.ts +33 -0
- package/dist/ui/chat/context/ChatContext.js +157 -0
- package/dist/ui/chat/hooks/useAutoScroll.d.ts +1 -0
- package/dist/ui/chat/hooks/useAutoScroll.js +11 -0
- package/dist/ui/chat/index.d.ts +7 -0
- package/dist/ui/chat/index.js +7 -0
- package/dist/ui/dashboard/ChartWidget.d.ts +18 -0
- package/dist/ui/dashboard/ChartWidget.js +98 -0
- package/dist/ui/dashboard/QueryWidget.d.ts +29 -0
- package/dist/ui/dashboard/QueryWidget.js +117 -0
- package/dist/ui/dashboard/TableWidget.d.ts +14 -0
- package/dist/ui/dashboard/TableWidget.js +117 -0
- package/dist/ui/dashboard/Widget.d.ts +40 -0
- package/dist/ui/dashboard/Widget.js +26 -0
- package/dist/ui/dashboard/WidgetBuilder.d.ts +47 -0
- package/dist/ui/dashboard/WidgetBuilder.js +286 -0
- package/dist/ui/dashboard/WidgetGrid.d.ts +19 -0
- package/dist/ui/dashboard/WidgetGrid.js +11 -0
- package/dist/ui/dashboard/index.d.ts +8 -0
- package/dist/ui/dashboard/index.js +4 -0
- package/dist/ui/dialogs/BrowserUrlDialog.d.ts +8 -0
- package/dist/ui/dialogs/BrowserUrlDialog.js +203 -0
- package/dist/ui/dialogs/index.d.ts +1 -0
- package/dist/ui/dialogs/index.js +1 -0
- package/dist/ui/editors/ImageEditor.d.ts +49 -0
- package/dist/ui/editors/ImageEditor.js +264 -0
- package/dist/ui/editors/index.d.ts +2 -0
- package/dist/ui/editors/index.js +1 -0
- package/dist/ui/execution/ExecutionFilters.d.ts +11 -0
- package/dist/ui/execution/ExecutionFilters.js +27 -0
- package/dist/ui/execution/ExecutionHistoryList.d.ts +19 -0
- package/dist/ui/execution/ExecutionHistoryList.js +35 -0
- package/dist/ui/execution/index.d.ts +3 -0
- package/dist/ui/execution/index.js +2 -0
- package/dist/ui/files/components/FileTree.d.ts +18 -0
- package/dist/ui/files/components/FileTree.js +61 -0
- package/dist/ui/files/components/Sidebar.d.ts +18 -0
- package/dist/ui/files/components/Sidebar.js +67 -0
- package/dist/ui/files/components/index.d.ts +2 -0
- package/dist/ui/files/components/index.js +2 -0
- package/dist/ui/files/context/FileSystemContext.d.ts +23 -0
- package/dist/ui/files/context/FileSystemContext.js +65 -0
- package/dist/ui/files/context/index.d.ts +1 -0
- package/dist/ui/files/context/index.js +1 -0
- package/dist/ui/files/index.d.ts +2 -0
- package/dist/ui/files/index.js +2 -0
- package/dist/ui/hooks/index.d.ts +4 -0
- package/dist/ui/hooks/index.js +3 -0
- package/dist/ui/hooks/useDebounce.d.ts +8 -0
- package/dist/ui/hooks/useDebounce.js +18 -0
- package/dist/ui/hooks/usePaneTracking.d.ts +15 -0
- package/dist/ui/hooks/usePaneTracking.js +20 -0
- package/dist/ui/hooks/useQuery.d.ts +25 -0
- package/dist/ui/hooks/useQuery.js +71 -0
- package/dist/ui/index.d.ts +19 -0
- package/dist/ui/index.js +20 -0
- package/dist/ui/jinx/JinxEditor.d.ts +20 -0
- package/dist/ui/jinx/JinxEditor.js +34 -0
- package/dist/ui/jinx/JinxTree.d.ts +14 -0
- package/dist/ui/jinx/JinxTree.js +65 -0
- package/dist/ui/jinx/index.d.ts +3 -0
- package/dist/ui/jinx/index.js +2 -0
- package/dist/ui/knowledge-graph/KGControls.d.ts +12 -0
- package/dist/ui/knowledge-graph/KGControls.js +21 -0
- package/dist/ui/knowledge-graph/KGStats.d.ts +13 -0
- package/dist/ui/knowledge-graph/KGStats.js +18 -0
- package/dist/ui/knowledge-graph/KnowledgeGraphViewer.d.ts +22 -0
- package/dist/ui/knowledge-graph/KnowledgeGraphViewer.js +16 -0
- package/dist/ui/knowledge-graph/index.d.ts +4 -0
- package/dist/ui/knowledge-graph/index.js +3 -0
- package/dist/ui/layout/components/AppShell.d.ts +8 -0
- package/dist/ui/layout/components/AppShell.js +19 -0
- package/dist/ui/layout/components/ContentPaneContainer.d.ts +8 -0
- package/dist/ui/layout/components/ContentPaneContainer.js +95 -0
- package/dist/ui/layout/components/LayoutNode.d.ts +8 -0
- package/dist/ui/layout/components/LayoutNode.js +12 -0
- package/dist/ui/layout/components/PaneHeader.d.ts +17 -0
- package/dist/ui/layout/components/PaneHeader.js +40 -0
- package/dist/ui/layout/components/SplitView.d.ts +8 -0
- package/dist/ui/layout/components/SplitView.js +51 -0
- package/dist/ui/layout/components/Studio.d.ts +7 -0
- package/dist/ui/layout/components/Studio.js +12 -0
- package/dist/ui/layout/components/contextMenus/BrowserContextMenu.d.ts +13 -0
- package/dist/ui/layout/components/contextMenus/BrowserContextMenu.js +23 -0
- package/dist/ui/layout/components/contextMenus/EditorContextMenu.d.ts +15 -0
- package/dist/ui/layout/components/contextMenus/EditorContextMenu.js +32 -0
- package/dist/ui/layout/components/contextMenus/FileContextMenu.d.ts +15 -0
- package/dist/ui/layout/components/contextMenus/FileContextMenu.js +33 -0
- package/dist/ui/layout/components/contextMenus/PdfContextMenu.d.ts +13 -0
- package/dist/ui/layout/components/contextMenus/PdfContextMenu.js +23 -0
- package/dist/ui/layout/components/contextMenus/index.d.ts +4 -0
- package/dist/ui/layout/components/contextMenus/index.js +4 -0
- package/dist/ui/layout/components/index.d.ts +8 -0
- package/dist/ui/layout/components/index.js +8 -0
- package/dist/ui/layout/components/modals/AIEditModal.d.ts +9 -0
- package/dist/ui/layout/components/modals/AIEditModal.js +33 -0
- package/dist/ui/layout/components/modals/MemoryApprovalModal.d.ts +15 -0
- package/dist/ui/layout/components/modals/MemoryApprovalModal.js +23 -0
- package/dist/ui/layout/components/modals/PromptModal.d.ts +11 -0
- package/dist/ui/layout/components/modals/PromptModal.js +20 -0
- package/dist/ui/layout/components/modals/ResendModal.d.ts +16 -0
- package/dist/ui/layout/components/modals/ResendModal.js +22 -0
- package/dist/ui/layout/components/modals/index.d.ts +4 -0
- package/dist/ui/layout/components/modals/index.js +4 -0
- package/dist/ui/layout/context/LayoutContext.d.ts +32 -0
- package/dist/ui/layout/context/LayoutContext.js +144 -0
- package/dist/ui/layout/index.d.ts +2 -0
- package/dist/ui/layout/index.js +2 -0
- package/dist/ui/markdown/Markdown.d.ts +4 -0
- package/dist/ui/markdown/Markdown.js +4 -0
- package/dist/ui/memory/MemoryFilters.d.ts +12 -0
- package/dist/ui/memory/MemoryFilters.js +23 -0
- package/dist/ui/memory/MemoryList.d.ts +19 -0
- package/dist/ui/memory/MemoryList.js +36 -0
- package/dist/ui/memory/index.d.ts +3 -0
- package/dist/ui/memory/index.js +2 -0
- package/dist/ui/models/ModelCard.d.ts +16 -0
- package/dist/ui/models/ModelCard.js +30 -0
- package/dist/ui/models/ModelSelector.d.ts +13 -0
- package/dist/ui/models/ModelSelector.js +6 -0
- package/dist/ui/models/index.d.ts +3 -0
- package/dist/ui/models/index.js +2 -0
- package/dist/ui/npc/McpServerMenu.d.ts +15 -0
- package/dist/ui/npc/McpServerMenu.js +48 -0
- package/dist/ui/npc/NPCEditor.d.ts +21 -0
- package/dist/ui/npc/NPCEditor.js +17 -0
- package/dist/ui/npc/NPCList.d.ts +19 -0
- package/dist/ui/npc/NPCList.js +28 -0
- package/dist/ui/npc/index.d.ts +3 -0
- package/dist/ui/npc/index.js +2 -0
- package/dist/ui/photo/GalleryGrid.d.ts +25 -0
- package/dist/ui/photo/GalleryGrid.js +46 -0
- package/dist/ui/photo/ImageAdjustmentSliders.d.ts +37 -0
- package/dist/ui/photo/ImageAdjustmentSliders.js +56 -0
- package/dist/ui/photo/ImageLabelingCanvas.d.ts +47 -0
- package/dist/ui/photo/ImageLabelingCanvas.js +174 -0
- package/dist/ui/photo/ImageSourceTabs.d.ts +28 -0
- package/dist/ui/photo/ImageSourceTabs.js +46 -0
- package/dist/ui/photo/LayerPanel.d.ts +87 -0
- package/dist/ui/photo/LayerPanel.js +70 -0
- package/dist/ui/photo/Lightbox.d.ts +12 -0
- package/dist/ui/photo/Lightbox.js +90 -0
- package/dist/ui/photo/index.d.ts +6 -0
- package/dist/ui/photo/index.js +7 -0
- package/dist/ui/primitives/AutosizeTextarea.d.ts +8 -0
- package/dist/ui/primitives/AutosizeTextarea.js +17 -0
- package/dist/ui/primitives/Button.d.ts +7 -0
- package/dist/ui/primitives/Button.js +17 -0
- package/dist/ui/primitives/Card.d.ts +12 -0
- package/dist/ui/primitives/Card.js +8 -0
- package/dist/ui/primitives/Chart.d.ts +34 -0
- package/dist/ui/primitives/Chart.js +140 -0
- package/dist/ui/primitives/ContextMenu.d.ts +17 -0
- package/dist/ui/primitives/ContextMenu.js +33 -0
- package/dist/ui/primitives/DataTable.d.ts +13 -0
- package/dist/ui/primitives/DataTable.js +13 -0
- package/dist/ui/primitives/FileUpload.d.ts +10 -0
- package/dist/ui/primitives/FileUpload.js +24 -0
- package/dist/ui/primitives/ImageGrid.d.ts +21 -0
- package/dist/ui/primitives/ImageGrid.js +29 -0
- package/dist/ui/primitives/Input.d.ts +7 -0
- package/dist/ui/primitives/Input.js +10 -0
- package/dist/ui/primitives/Lightbox.d.ts +12 -0
- package/dist/ui/primitives/Lightbox.js +36 -0
- package/dist/ui/primitives/Modal.d.ts +10 -0
- package/dist/ui/primitives/Modal.js +33 -0
- package/dist/ui/primitives/RangeSlider.d.ts +13 -0
- package/dist/ui/primitives/RangeSlider.js +6 -0
- package/dist/ui/primitives/Select.d.ts +11 -0
- package/dist/ui/primitives/Select.js +10 -0
- package/dist/ui/primitives/Slider.d.ts +13 -0
- package/dist/ui/primitives/Slider.js +8 -0
- package/dist/ui/primitives/SortableList.d.ts +9 -0
- package/dist/ui/primitives/SortableList.js +19 -0
- package/dist/ui/primitives/Spinner.d.ts +2 -0
- package/dist/ui/primitives/Spinner.js +2 -0
- package/dist/ui/primitives/StarRating.d.ts +10 -0
- package/dist/ui/primitives/StarRating.js +10 -0
- package/dist/ui/primitives/Tabs.d.ts +15 -0
- package/dist/ui/primitives/Tabs.js +11 -0
- package/dist/ui/primitives/TagInput.d.ts +10 -0
- package/dist/ui/primitives/TagInput.js +25 -0
- package/dist/ui/primitives/index.d.ts +20 -0
- package/dist/ui/primitives/index.js +19 -0
- package/dist/ui/specialized/DiskUsageAnalyzer.d.ts +12 -0
- package/dist/ui/specialized/DiskUsageAnalyzer.js +38 -0
- package/dist/ui/specialized/OllamaModelManager.d.ts +2 -0
- package/dist/ui/specialized/OllamaModelManager.js +48 -0
- package/dist/ui/specialized/RichTextEditor.d.ts +8 -0
- package/dist/ui/specialized/RichTextEditor.js +64 -0
- package/dist/ui/specialized/SlideCanvas.d.ts +24 -0
- package/dist/ui/specialized/SlideCanvas.js +31 -0
- package/dist/ui/specialized/SpreadsheetGrid.d.ts +18 -0
- package/dist/ui/specialized/SpreadsheetGrid.js +28 -0
- package/dist/ui/specialized/StepEditor.d.ts +12 -0
- package/dist/ui/specialized/StepEditor.js +22 -0
- package/dist/ui/specialized/TerminalEmbed.d.ts +13 -0
- package/dist/ui/specialized/TerminalEmbed.js +50 -0
- package/dist/ui/specialized/index.d.ts +7 -0
- package/dist/ui/specialized/index.js +7 -0
- package/dist/ui/sql/SQLQueryEditor.d.ts +9 -0
- package/dist/ui/sql/SQLQueryEditor.js +10 -0
- package/dist/ui/sql/SQLResultsTable.d.ts +6 -0
- package/dist/ui/sql/SQLResultsTable.js +12 -0
- package/dist/ui/sql/SQLSchemaViewer.d.ts +14 -0
- package/dist/ui/sql/SQLSchemaViewer.js +18 -0
- package/dist/ui/sql/index.d.ts +3 -0
- package/dist/ui/sql/index.js +3 -0
- package/dist/ui/utils/cn.d.ts +1 -0
- package/dist/ui/utils/cn.js +1 -0
- package/dist/ui/utils/fileIcons.d.ts +2 -0
- package/dist/ui/utils/fileIcons.js +24 -0
- package/dist/ui/utils/fileUtils.d.ts +4 -0
- package/dist/ui/utils/fileUtils.js +11 -0
- package/dist/ui/utils/index.d.ts +3 -0
- package/dist/ui/utils/index.js +3 -0
- package/dist/ui/viewers/components/BrowserViewer.d.ts +9 -0
- package/dist/ui/viewers/components/BrowserViewer.js +44 -0
- package/dist/ui/viewers/components/CodeEditor.d.ts +11 -0
- package/dist/ui/viewers/components/CodeEditor.js +115 -0
- package/dist/ui/viewers/components/CsvViewer.d.ts +7 -0
- package/dist/ui/viewers/components/CsvViewer.js +18 -0
- package/dist/ui/viewers/components/ImageViewer.d.ts +7 -0
- package/dist/ui/viewers/components/ImageViewer.js +23 -0
- package/dist/ui/viewers/components/PdfViewer.d.ts +7 -0
- package/dist/ui/viewers/components/PdfViewer.js +29 -0
- package/dist/ui/viewers/components/Terminal.d.ts +7 -0
- package/dist/ui/viewers/components/Terminal.js +31 -0
- package/dist/ui/viewers/components/index.d.ts +6 -0
- package/dist/ui/viewers/components/index.js +6 -0
- package/dist/ui/viewers/index.d.ts +1 -0
- package/dist/ui/viewers/index.js +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
import { ChevronUp, ChevronDown, Search, Download, Copy } from 'lucide-react';
|
|
3
|
+
export const TableWidget = ({ data, columns: propColumns, maxHeight = 400, sortable = true, searchable = true, pagination = true, pageSize = 10, onRowClick, onExport }) => {
|
|
4
|
+
const [sortColumn, setSortColumn] = useState(null);
|
|
5
|
+
const [sortDirection, setSortDirection] = useState('asc');
|
|
6
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
7
|
+
const [currentPage, setCurrentPage] = useState(0);
|
|
8
|
+
// Derive columns from data if not provided
|
|
9
|
+
const columns = useMemo(() => {
|
|
10
|
+
if (propColumns)
|
|
11
|
+
return propColumns;
|
|
12
|
+
if (data.length === 0)
|
|
13
|
+
return [];
|
|
14
|
+
return Object.keys(data[0]);
|
|
15
|
+
}, [data, propColumns]);
|
|
16
|
+
// Filter data by search term
|
|
17
|
+
const filteredData = useMemo(() => {
|
|
18
|
+
if (!searchTerm)
|
|
19
|
+
return data;
|
|
20
|
+
const term = searchTerm.toLowerCase();
|
|
21
|
+
return data.filter(row => columns.some(col => String(row[col] ?? '').toLowerCase().includes(term)));
|
|
22
|
+
}, [data, columns, searchTerm]);
|
|
23
|
+
// Sort data
|
|
24
|
+
const sortedData = useMemo(() => {
|
|
25
|
+
if (!sortColumn)
|
|
26
|
+
return filteredData;
|
|
27
|
+
return [...filteredData].sort((a, b) => {
|
|
28
|
+
const aVal = a[sortColumn];
|
|
29
|
+
const bVal = b[sortColumn];
|
|
30
|
+
if (aVal === null || aVal === undefined)
|
|
31
|
+
return 1;
|
|
32
|
+
if (bVal === null || bVal === undefined)
|
|
33
|
+
return -1;
|
|
34
|
+
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
35
|
+
return sortDirection === 'asc' ? aVal - bVal : bVal - aVal;
|
|
36
|
+
}
|
|
37
|
+
const aStr = String(aVal);
|
|
38
|
+
const bStr = String(bVal);
|
|
39
|
+
return sortDirection === 'asc'
|
|
40
|
+
? aStr.localeCompare(bStr)
|
|
41
|
+
: bStr.localeCompare(aStr);
|
|
42
|
+
});
|
|
43
|
+
}, [filteredData, sortColumn, sortDirection]);
|
|
44
|
+
// Paginate data
|
|
45
|
+
const paginatedData = useMemo(() => {
|
|
46
|
+
if (!pagination)
|
|
47
|
+
return sortedData;
|
|
48
|
+
const start = currentPage * pageSize;
|
|
49
|
+
return sortedData.slice(start, start + pageSize);
|
|
50
|
+
}, [sortedData, pagination, currentPage, pageSize]);
|
|
51
|
+
const totalPages = Math.ceil(sortedData.length / pageSize);
|
|
52
|
+
const handleSort = (column) => {
|
|
53
|
+
if (!sortable)
|
|
54
|
+
return;
|
|
55
|
+
if (sortColumn === column) {
|
|
56
|
+
setSortDirection(d => d === 'asc' ? 'desc' : 'asc');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
setSortColumn(column);
|
|
60
|
+
setSortDirection('asc');
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const handleCopyCell = (value) => {
|
|
64
|
+
navigator.clipboard.writeText(String(value ?? ''));
|
|
65
|
+
};
|
|
66
|
+
if (data.length === 0) {
|
|
67
|
+
return (React.createElement("div", { className: "flex items-center justify-center h-32 text-gray-500" }, "No data available"));
|
|
68
|
+
}
|
|
69
|
+
return (React.createElement("div", { className: "flex flex-col h-full" },
|
|
70
|
+
(searchable || onExport) && (React.createElement("div", { className: "flex items-center gap-2 mb-2" },
|
|
71
|
+
searchable && (React.createElement("div", { className: "flex-1 relative" },
|
|
72
|
+
React.createElement(Search, { size: 14, className: "absolute left-2 top-1/2 -translate-y-1/2 text-gray-500" }),
|
|
73
|
+
React.createElement("input", { type: "text", value: searchTerm, onChange: (e) => {
|
|
74
|
+
setSearchTerm(e.target.value);
|
|
75
|
+
setCurrentPage(0);
|
|
76
|
+
}, placeholder: "Search...", className: "w-full pl-8 pr-3 py-1.5 bg-gray-800 border border-gray-700 rounded text-sm" }))),
|
|
77
|
+
onExport && (React.createElement("button", { onClick: () => onExport(sortedData), className: "p-1.5 hover:bg-gray-700 rounded text-gray-400", title: "Export" },
|
|
78
|
+
React.createElement(Download, { size: 14 }))))),
|
|
79
|
+
React.createElement("div", { className: "flex-1 overflow-auto border border-gray-700 rounded", style: { maxHeight } },
|
|
80
|
+
React.createElement("table", { className: "w-full text-sm" },
|
|
81
|
+
React.createElement("thead", { className: "sticky top-0 bg-gray-800 z-10" },
|
|
82
|
+
React.createElement("tr", null, columns.map(col => (React.createElement("th", { key: col, onClick: () => handleSort(col), className: `px-3 py-2 text-left font-medium text-gray-300 border-b border-gray-700
|
|
83
|
+
${sortable ? 'cursor-pointer hover:bg-gray-700' : ''}` },
|
|
84
|
+
React.createElement("div", { className: "flex items-center gap-1" },
|
|
85
|
+
React.createElement("span", { className: "truncate" }, col),
|
|
86
|
+
sortable && sortColumn === col && (sortDirection === 'asc'
|
|
87
|
+
? React.createElement(ChevronUp, { size: 12 })
|
|
88
|
+
: React.createElement(ChevronDown, { size: 12 })))))))),
|
|
89
|
+
React.createElement("tbody", null, paginatedData.map((row, rowIndex) => (React.createElement("tr", { key: rowIndex, onClick: () => onRowClick?.(row, rowIndex), className: `border-b border-gray-800 hover:bg-gray-800/50
|
|
90
|
+
${onRowClick ? 'cursor-pointer' : ''}` }, columns.map(col => (React.createElement("td", { key: col, className: "px-3 py-2 text-gray-300 group relative" },
|
|
91
|
+
React.createElement("div", { className: "truncate max-w-[200px]", title: String(row[col] ?? '') }, formatCellValue(row[col])),
|
|
92
|
+
React.createElement("button", { onClick: (e) => {
|
|
93
|
+
e.stopPropagation();
|
|
94
|
+
handleCopyCell(row[col]);
|
|
95
|
+
}, className: "absolute right-1 top-1/2 -translate-y-1/2 p-1\n opacity-0 group-hover:opacity-100 hover:bg-gray-700 rounded" },
|
|
96
|
+
React.createElement(Copy, { size: 10 }))))))))))),
|
|
97
|
+
pagination && totalPages > 1 && (React.createElement("div", { className: "flex items-center justify-between mt-2 text-sm text-gray-400" },
|
|
98
|
+
React.createElement("span", null,
|
|
99
|
+
currentPage * pageSize + 1,
|
|
100
|
+
"-",
|
|
101
|
+
Math.min((currentPage + 1) * pageSize, sortedData.length),
|
|
102
|
+
" of ",
|
|
103
|
+
sortedData.length),
|
|
104
|
+
React.createElement("div", { className: "flex gap-1" },
|
|
105
|
+
React.createElement("button", { onClick: () => setCurrentPage(p => Math.max(0, p - 1)), disabled: currentPage === 0, className: "px-2 py-1 rounded hover:bg-gray-700 disabled:opacity-50" }, "Prev"),
|
|
106
|
+
React.createElement("button", { onClick: () => setCurrentPage(p => Math.min(totalPages - 1, p + 1)), disabled: currentPage >= totalPages - 1, className: "px-2 py-1 rounded hover:bg-gray-700 disabled:opacity-50" }, "Next"))))));
|
|
107
|
+
};
|
|
108
|
+
function formatCellValue(value) {
|
|
109
|
+
if (value === null || value === undefined)
|
|
110
|
+
return '—';
|
|
111
|
+
if (typeof value === 'boolean')
|
|
112
|
+
return value ? 'Yes' : 'No';
|
|
113
|
+
if (typeof value === 'object')
|
|
114
|
+
return JSON.stringify(value);
|
|
115
|
+
return String(value);
|
|
116
|
+
}
|
|
117
|
+
export default TableWidget;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface WidgetProps {
|
|
3
|
+
title: string;
|
|
4
|
+
icon?: React.ReactNode;
|
|
5
|
+
iconColor?: string;
|
|
6
|
+
loading?: boolean;
|
|
7
|
+
error?: string | null;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
headerActions?: React.ReactNode;
|
|
11
|
+
onContextMenu?: (e: React.MouseEvent) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare const Widget: React.FC<WidgetProps>;
|
|
14
|
+
export interface StatWidgetProps {
|
|
15
|
+
value: string | number;
|
|
16
|
+
label?: string;
|
|
17
|
+
trend?: {
|
|
18
|
+
value: number;
|
|
19
|
+
direction: 'up' | 'down';
|
|
20
|
+
};
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const StatValue: React.FC<StatWidgetProps>;
|
|
24
|
+
export interface StatListProps {
|
|
25
|
+
items: Array<{
|
|
26
|
+
label: string;
|
|
27
|
+
value: string | number;
|
|
28
|
+
}>;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare const StatList: React.FC<StatListProps>;
|
|
32
|
+
export interface WidgetToggleProps {
|
|
33
|
+
options: Array<{
|
|
34
|
+
label: string;
|
|
35
|
+
value: string;
|
|
36
|
+
}>;
|
|
37
|
+
activeValue: string;
|
|
38
|
+
onChange: (value: string) => void;
|
|
39
|
+
}
|
|
40
|
+
export declare const WidgetToggle: React.FC<WidgetToggleProps>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Loader } from 'lucide-react';
|
|
3
|
+
export const Widget = ({ title, icon, iconColor = 'text-gray-400', loading = false, error = null, children, className = '', headerActions, onContextMenu }) => {
|
|
4
|
+
return (React.createElement("div", { className: `theme-bg-tertiary p-4 rounded-lg flex flex-col h-full ${className}`, onContextMenu: onContextMenu },
|
|
5
|
+
React.createElement("div", { className: "flex justify-between items-start flex-shrink-0 mb-2" },
|
|
6
|
+
React.createElement("div", { className: "flex items-center gap-3 flex-1" },
|
|
7
|
+
icon && React.createElement("span", { className: iconColor }, icon),
|
|
8
|
+
React.createElement("h4", { className: "font-semibold theme-text-secondary truncate" }, title)),
|
|
9
|
+
headerActions),
|
|
10
|
+
React.createElement("div", { className: "flex-1 overflow-hidden" }, loading ? (React.createElement("div", { className: "flex items-center justify-center h-full" },
|
|
11
|
+
React.createElement(Loader, { className: "animate-spin text-blue-400", size: 24 }))) : error ? (React.createElement("div", { className: "text-red-400 p-2 text-xs overflow-auto" }, error)) : (children))));
|
|
12
|
+
};
|
|
13
|
+
export const StatValue = ({ value, label, trend, className = '' }) => (React.createElement("div", { className: `flex flex-col ${className}` },
|
|
14
|
+
React.createElement("p", { className: "text-3xl font-bold theme-text-primary" }, value),
|
|
15
|
+
label && React.createElement("p", { className: "text-sm theme-text-secondary mt-1" }, label),
|
|
16
|
+
trend && (React.createElement("p", { className: `text-sm mt-1 ${trend.direction === 'up' ? 'text-green-400' : 'text-red-400'}` },
|
|
17
|
+
trend.direction === 'up' ? '↑' : '↓',
|
|
18
|
+
" ",
|
|
19
|
+
Math.abs(trend.value),
|
|
20
|
+
"%"))));
|
|
21
|
+
export const StatList = ({ items, className = '' }) => (React.createElement("ul", { className: `space-y-1 text-sm theme-text-secondary ${className}` }, items.map((item, i) => (React.createElement("li", { key: i, className: "flex justify-between" },
|
|
22
|
+
React.createElement("span", null, item.label),
|
|
23
|
+
React.createElement("span", { className: "font-bold" }, item.value))))));
|
|
24
|
+
export const WidgetToggle = ({ options, activeValue, onChange }) => (React.createElement("div", { className: "flex items-center gap-1" }, options.map(opt => (React.createElement("button", { key: opt.value, onClick: () => onChange(opt.value), className: `px-2 py-0.5 text-xs rounded ${activeValue === opt.value
|
|
25
|
+
? 'theme-button-primary'
|
|
26
|
+
: 'theme-button theme-hover'}` }, opt.label)))));
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface WidgetConfig {
|
|
3
|
+
id?: string;
|
|
4
|
+
title: string;
|
|
5
|
+
type: 'table' | 'chart' | 'stat' | 'stat_list' | 'line_chart' | 'bar_chart';
|
|
6
|
+
query: string;
|
|
7
|
+
iconName?: string;
|
|
8
|
+
iconColor?: string;
|
|
9
|
+
span?: number;
|
|
10
|
+
chartConfig?: {
|
|
11
|
+
x: string;
|
|
12
|
+
y: string;
|
|
13
|
+
type: 'line' | 'bar';
|
|
14
|
+
groupBy?: string;
|
|
15
|
+
};
|
|
16
|
+
toggleOptions?: Array<{
|
|
17
|
+
label: string;
|
|
18
|
+
modifier: string;
|
|
19
|
+
}>;
|
|
20
|
+
builder?: {
|
|
21
|
+
table: string;
|
|
22
|
+
selectedColumns: string[];
|
|
23
|
+
selectExpressions?: string[];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export interface SchemaColumn {
|
|
27
|
+
name: string;
|
|
28
|
+
type: string;
|
|
29
|
+
}
|
|
30
|
+
export interface WidgetBuilderProps {
|
|
31
|
+
isOpen: boolean;
|
|
32
|
+
onClose: () => void;
|
|
33
|
+
onSave: (widget: WidgetConfig) => void;
|
|
34
|
+
widget?: Partial<WidgetConfig>;
|
|
35
|
+
tables: string[];
|
|
36
|
+
fetchSchema: (tableName: string) => Promise<SchemaColumn[]>;
|
|
37
|
+
executeQuery?: (query: string) => Promise<{
|
|
38
|
+
result?: unknown[];
|
|
39
|
+
error?: string;
|
|
40
|
+
}>;
|
|
41
|
+
context?: {
|
|
42
|
+
query?: string;
|
|
43
|
+
result?: Record<string, unknown>[];
|
|
44
|
+
};
|
|
45
|
+
generateId?: () => string;
|
|
46
|
+
}
|
|
47
|
+
export declare const WidgetBuilder: React.FC<WidgetBuilderProps>;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { X, Play, Loader } from 'lucide-react';
|
|
3
|
+
const defaultGenerateId = () => `widget_${Math.random().toString(36).substring(2, 11)}`;
|
|
4
|
+
const WIDGET_TYPES = [
|
|
5
|
+
{ value: 'table', label: 'Table' },
|
|
6
|
+
{ value: 'chart', label: 'Chart' },
|
|
7
|
+
{ value: 'stat', label: 'Single Stat' },
|
|
8
|
+
{ value: 'stat_list', label: 'Stat List' },
|
|
9
|
+
];
|
|
10
|
+
const CHART_TYPES = [
|
|
11
|
+
{ value: 'bar', label: 'Bar Chart' },
|
|
12
|
+
{ value: 'line', label: 'Line Chart' },
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Parses a SQL query to extract builder configuration
|
|
16
|
+
*/
|
|
17
|
+
const parseQueryForBuilder = (query) => {
|
|
18
|
+
if (!query) {
|
|
19
|
+
return { isComplex: false, builderConfig: {} };
|
|
20
|
+
}
|
|
21
|
+
// Check for complex query patterns
|
|
22
|
+
const complexityPattern = /\bJOIN\b|\bUNION\b|\bWITH\b/i;
|
|
23
|
+
const isComplex = complexityPattern.test(query);
|
|
24
|
+
if (isComplex) {
|
|
25
|
+
return { isComplex: true, builderConfig: {} };
|
|
26
|
+
}
|
|
27
|
+
// Extract table name
|
|
28
|
+
const fromMatch = query.match(/\bFROM\s+([a-zA-Z0-9_]+)/i);
|
|
29
|
+
if (!fromMatch) {
|
|
30
|
+
return { isComplex: true, builderConfig: {} };
|
|
31
|
+
}
|
|
32
|
+
const table = fromMatch[1];
|
|
33
|
+
// Extract SELECT expressions
|
|
34
|
+
const selectMatch = query.match(/\bSELECT\s+(.*?)(?=\bFROM)/is);
|
|
35
|
+
const selectExpressions = selectMatch
|
|
36
|
+
? selectMatch[1].split(',').map(s => s.trim())
|
|
37
|
+
: ['*'];
|
|
38
|
+
// Extract columns from expressions
|
|
39
|
+
const extractedBaseColumns = new Set();
|
|
40
|
+
const keywordBlacklist = new Set([
|
|
41
|
+
'COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'DISTINCT', 'FROM', 'WHERE',
|
|
42
|
+
'GROUP', 'ORDER', 'BY', 'LIMIT', 'CASE', 'WHEN', 'THEN', 'ELSE',
|
|
43
|
+
'END', 'AS', 'IN', 'LIKE', 'IS', 'BETWEEN', 'AND', 'OR', 'NOT',
|
|
44
|
+
'NULL', 'STRFTIME', 'LENGTH'
|
|
45
|
+
]);
|
|
46
|
+
selectExpressions.forEach(expr => {
|
|
47
|
+
const columnCandidates = expr.matchAll(/\b([a-zA-Z0-9_]+)\b/g);
|
|
48
|
+
for (const match of columnCandidates) {
|
|
49
|
+
if (match[1] && !keywordBlacklist.has(match[1].toUpperCase())) {
|
|
50
|
+
extractedBaseColumns.add(match[1]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
isComplex: false,
|
|
56
|
+
builderConfig: {
|
|
57
|
+
table,
|
|
58
|
+
selectedColumns: Array.from(extractedBaseColumns),
|
|
59
|
+
selectExpressions
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
export const WidgetBuilder = ({ isOpen, onClose, onSave, widget, tables, fetchSchema, executeQuery, context, generateId = defaultGenerateId }) => {
|
|
64
|
+
const isEditMode = Boolean(widget?.id);
|
|
65
|
+
const parsedData = widget?.query ? parseQueryForBuilder(widget.query) : { isComplex: false, builderConfig: {} };
|
|
66
|
+
// Mode state
|
|
67
|
+
const [mode, setMode] = useState(parsedData.isComplex ? 'advanced' : 'builder');
|
|
68
|
+
const isComplexQuery = parsedData.isComplex;
|
|
69
|
+
// Form state
|
|
70
|
+
const [title, setTitle] = useState(widget?.title || '');
|
|
71
|
+
const [type, setType] = useState(widget?.type || 'table');
|
|
72
|
+
const [query, setQuery] = useState(widget?.query || '');
|
|
73
|
+
const [selectedTable, setSelectedTable] = useState(parsedData.builderConfig.table || '');
|
|
74
|
+
const [selectedColumns, setSelectedColumns] = useState(parsedData.builderConfig.selectedColumns || []);
|
|
75
|
+
// Chart config
|
|
76
|
+
const [xCol, setXCol] = useState(widget?.chartConfig?.x || '');
|
|
77
|
+
const [yCol, setYCol] = useState(widget?.chartConfig?.y || '');
|
|
78
|
+
const [chartType, setChartType] = useState(widget?.chartConfig?.type || 'bar');
|
|
79
|
+
const [groupBy, setGroupBy] = useState(widget?.chartConfig?.groupBy || '');
|
|
80
|
+
// Toggle options for filtering
|
|
81
|
+
const [toggleOptions, setToggleOptions] = useState(widget?.toggleOptions || []);
|
|
82
|
+
// Schema/columns state
|
|
83
|
+
const [availableColumns, setAvailableColumns] = useState([]);
|
|
84
|
+
const [outputColumns, setOutputColumns] = useState([]);
|
|
85
|
+
// Testing state
|
|
86
|
+
const [testStatus, setTestStatus] = useState({ loading: false, error: null });
|
|
87
|
+
// Initialize from context if provided
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (context?.result?.[0] && !isEditMode) {
|
|
90
|
+
const columns = Object.keys(context.result[0]);
|
|
91
|
+
setOutputColumns(columns.map(name => ({ name, type: 'RESULT_COL' })));
|
|
92
|
+
setXCol(columns[0] || '');
|
|
93
|
+
setYCol(columns.length > 1 ? columns[1] : '');
|
|
94
|
+
setQuery(context.query || '');
|
|
95
|
+
setType('chart');
|
|
96
|
+
setMode('advanced');
|
|
97
|
+
}
|
|
98
|
+
}, [context, isEditMode]);
|
|
99
|
+
// Load schema when table changes
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (selectedTable && mode === 'builder') {
|
|
102
|
+
fetchSchema(selectedTable).then(schema => {
|
|
103
|
+
setAvailableColumns(schema || []);
|
|
104
|
+
if (!type.includes('chart')) {
|
|
105
|
+
setOutputColumns(schema || []);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}, [selectedTable, fetchSchema, mode, type]);
|
|
110
|
+
// Auto-generate query in builder mode
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (mode === 'builder' && selectedTable && !type.includes('chart')) {
|
|
113
|
+
const cols = selectedColumns.length > 0 ? selectedColumns.join(', ') : '*';
|
|
114
|
+
setQuery(`SELECT ${cols} FROM ${selectedTable}`);
|
|
115
|
+
}
|
|
116
|
+
}, [mode, selectedTable, selectedColumns, type]);
|
|
117
|
+
// Test query and get output columns
|
|
118
|
+
const testQuery = useCallback(async () => {
|
|
119
|
+
if (!executeQuery || !query)
|
|
120
|
+
return;
|
|
121
|
+
setTestStatus({ loading: true, error: null });
|
|
122
|
+
try {
|
|
123
|
+
const testQ = `${query.replace(/;$/, '')} LIMIT 1`;
|
|
124
|
+
const result = await executeQuery(testQ);
|
|
125
|
+
if (result.error) {
|
|
126
|
+
throw new Error(result.error);
|
|
127
|
+
}
|
|
128
|
+
if (result.result && Array.isArray(result.result) && result.result.length > 0) {
|
|
129
|
+
const cols = Object.keys(result.result[0]);
|
|
130
|
+
setOutputColumns(cols.map(name => ({ name, type: 'RESULT_COL' })));
|
|
131
|
+
}
|
|
132
|
+
setTestStatus({ loading: false, error: null });
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
setTestStatus({ loading: false, error: err.message });
|
|
136
|
+
}
|
|
137
|
+
}, [executeQuery, query]);
|
|
138
|
+
const handleSave = () => {
|
|
139
|
+
let finalQuery = query;
|
|
140
|
+
// Build query in builder mode for charts
|
|
141
|
+
if (mode === 'builder' && type.includes('chart') && selectedTable) {
|
|
142
|
+
const selectParts = [];
|
|
143
|
+
if (xCol)
|
|
144
|
+
selectParts.push(xCol);
|
|
145
|
+
if (yCol) {
|
|
146
|
+
yCol.split(',').forEach(expr => {
|
|
147
|
+
const trimmed = expr.trim();
|
|
148
|
+
if (trimmed && !selectParts.includes(trimmed)) {
|
|
149
|
+
selectParts.push(trimmed);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (selectParts.length > 0) {
|
|
154
|
+
finalQuery = `SELECT ${selectParts.join(', ')} FROM ${selectedTable}`;
|
|
155
|
+
if (groupBy) {
|
|
156
|
+
finalQuery += ` GROUP BY ${groupBy}`;
|
|
157
|
+
}
|
|
158
|
+
else if (xCol && selectParts.length > 1) {
|
|
159
|
+
const xBase = xCol.split(/\s+AS\s+/i)[0].trim();
|
|
160
|
+
finalQuery += ` GROUP BY ${xBase}`;
|
|
161
|
+
}
|
|
162
|
+
if (xCol) {
|
|
163
|
+
const xBase = xCol.split(/\s+AS\s+/i)[0].trim();
|
|
164
|
+
finalQuery += ` ORDER BY ${xBase}`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const widgetConfig = {
|
|
169
|
+
id: widget?.id || generateId(),
|
|
170
|
+
title: title || 'Custom Widget',
|
|
171
|
+
type,
|
|
172
|
+
query: finalQuery,
|
|
173
|
+
iconName: widget?.iconName || 'Settings2',
|
|
174
|
+
iconColor: widget?.iconColor || 'text-blue-400',
|
|
175
|
+
span: type.includes('chart') ? 2 : 1,
|
|
176
|
+
chartConfig: type.includes('chart') ? {
|
|
177
|
+
x: xCol,
|
|
178
|
+
y: yCol,
|
|
179
|
+
type: chartType,
|
|
180
|
+
groupBy: groupBy || undefined
|
|
181
|
+
} : undefined,
|
|
182
|
+
toggleOptions: toggleOptions.length > 0 ? toggleOptions : undefined,
|
|
183
|
+
builder: mode === 'builder' ? {
|
|
184
|
+
table: selectedTable,
|
|
185
|
+
selectedColumns
|
|
186
|
+
} : undefined
|
|
187
|
+
};
|
|
188
|
+
onSave(widgetConfig);
|
|
189
|
+
onClose();
|
|
190
|
+
};
|
|
191
|
+
const handleToggleChange = (index, field, value) => {
|
|
192
|
+
const newToggles = [...toggleOptions];
|
|
193
|
+
newToggles[index][field] = value;
|
|
194
|
+
setToggleOptions(newToggles);
|
|
195
|
+
};
|
|
196
|
+
const addToggle = () => {
|
|
197
|
+
setToggleOptions([...toggleOptions, { label: 'New', modifier: '' }]);
|
|
198
|
+
};
|
|
199
|
+
const removeToggle = (index) => {
|
|
200
|
+
setToggleOptions(toggleOptions.filter((_, i) => i !== index));
|
|
201
|
+
};
|
|
202
|
+
if (!isOpen)
|
|
203
|
+
return null;
|
|
204
|
+
return (React.createElement("div", { className: "fixed inset-0 bg-black/70 flex items-center justify-center z-[60]" },
|
|
205
|
+
React.createElement("div", { className: "theme-bg-secondary p-6 rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] flex flex-col" },
|
|
206
|
+
React.createElement("div", { className: "flex justify-between items-center mb-4 flex-shrink-0" },
|
|
207
|
+
React.createElement("h3", { className: "text-lg font-semibold" }, isEditMode ? 'Edit Widget' : 'Create Widget'),
|
|
208
|
+
React.createElement("button", { onClick: onClose, className: "p-1 rounded-full theme-hover" },
|
|
209
|
+
React.createElement(X, { size: 20 }))),
|
|
210
|
+
React.createElement("div", { className: "flex border-b theme-border mb-4 flex-shrink-0" },
|
|
211
|
+
React.createElement("button", { onClick: () => !isComplexQuery && setMode('builder'), className: `px-4 py-2 text-sm ${mode === 'builder' ? 'border-b-2 border-blue-500' : 'theme-text-secondary'} ${isComplexQuery ? 'opacity-50 cursor-not-allowed' : ''}`, title: isComplexQuery ? "Cannot use builder for complex queries" : "" }, "Builder"),
|
|
212
|
+
React.createElement("button", { onClick: () => setMode('advanced'), className: `px-4 py-2 text-sm ${mode === 'advanced' ? 'border-b-2 border-blue-500' : 'theme-text-secondary'}` }, "Advanced SQL")),
|
|
213
|
+
React.createElement("div", { className: "flex-1 overflow-y-auto space-y-4 pr-2" },
|
|
214
|
+
React.createElement("div", { className: "p-3 border theme-border rounded-lg theme-bg-tertiary" },
|
|
215
|
+
React.createElement("label", { className: "text-xs font-semibold theme-text-secondary uppercase tracking-wider" }, "Title"),
|
|
216
|
+
React.createElement("input", { type: "text", value: title, onChange: e => setTitle(e.target.value), className: "w-full theme-input mt-1", placeholder: "e.g., Daily Active Users" })),
|
|
217
|
+
React.createElement("div", { className: "p-3 border theme-border rounded-lg theme-bg-tertiary space-y-3" },
|
|
218
|
+
React.createElement("h4", { className: "text-sm font-semibold theme-text-primary" }, "Data Source"),
|
|
219
|
+
mode === 'builder' ? (React.createElement(React.Fragment, null,
|
|
220
|
+
React.createElement("div", null,
|
|
221
|
+
React.createElement("label", { className: "text-xs font-semibold theme-text-secondary uppercase tracking-wider" }, "Table"),
|
|
222
|
+
React.createElement("select", { value: selectedTable, onChange: e => {
|
|
223
|
+
setSelectedTable(e.target.value);
|
|
224
|
+
setSelectedColumns([]);
|
|
225
|
+
}, className: "w-full theme-input mt-1" },
|
|
226
|
+
React.createElement("option", { value: "" }, "Select a table..."),
|
|
227
|
+
tables.map(t => (React.createElement("option", { key: t, value: t }, t))))),
|
|
228
|
+
selectedTable && !type.includes('chart') && (React.createElement("div", null,
|
|
229
|
+
React.createElement("label", { className: "text-xs font-semibold theme-text-secondary uppercase tracking-wider" }, "Columns"),
|
|
230
|
+
React.createElement("div", { className: "max-h-32 overflow-y-auto theme-bg-primary p-2 rounded mt-1" }, availableColumns.map(col => (React.createElement("div", { key: col.name, className: "flex items-center" },
|
|
231
|
+
React.createElement("input", { type: "checkbox", id: `col-${col.name}`, checked: selectedColumns.includes(col.name), onChange: e => {
|
|
232
|
+
setSelectedColumns(prev => e.target.checked
|
|
233
|
+
? [...prev, col.name]
|
|
234
|
+
: prev.filter(c => c !== col.name));
|
|
235
|
+
}, className: "w-4 h-4 theme-checkbox" }),
|
|
236
|
+
React.createElement("label", { htmlFor: `col-${col.name}`, className: "ml-2 text-sm" },
|
|
237
|
+
col.name,
|
|
238
|
+
" ",
|
|
239
|
+
React.createElement("span", { className: "text-yellow-400" },
|
|
240
|
+
"(",
|
|
241
|
+
col.type,
|
|
242
|
+
")")))))))))) : (React.createElement("div", null,
|
|
243
|
+
React.createElement("label", { className: "text-xs font-semibold theme-text-secondary uppercase tracking-wider" }, "SQL Query"),
|
|
244
|
+
React.createElement("textarea", { value: query, onChange: e => setQuery(e.target.value), rows: 6, className: "w-full theme-input mt-1 font-mono text-sm", placeholder: "SELECT * FROM table_name LIMIT 100" }),
|
|
245
|
+
executeQuery && (React.createElement("button", { onClick: testQuery, className: "text-xs theme-button-subtle mt-2 flex items-center gap-1", disabled: testStatus.loading },
|
|
246
|
+
testStatus.loading ? React.createElement(Loader, { size: 12, className: "animate-spin" }) : React.createElement(Play, { size: 12 }),
|
|
247
|
+
testStatus.loading ? 'Testing...' : 'Test Query & Get Columns')),
|
|
248
|
+
testStatus.error && (React.createElement("p", { className: "text-red-400 text-xs mt-1" }, testStatus.error))))),
|
|
249
|
+
React.createElement("div", { className: "p-3 border theme-border rounded-lg theme-bg-tertiary space-y-3" },
|
|
250
|
+
React.createElement("h4", { className: "text-sm font-semibold theme-text-primary" }, "Visualization"),
|
|
251
|
+
React.createElement("div", null,
|
|
252
|
+
React.createElement("label", { className: "text-xs font-semibold theme-text-secondary uppercase tracking-wider" }, "Display As"),
|
|
253
|
+
React.createElement("select", { value: type, onChange: e => setType(e.target.value), className: "w-full theme-input mt-1" }, WIDGET_TYPES.map(opt => (React.createElement("option", { key: opt.value, value: opt.value }, opt.label))))),
|
|
254
|
+
type.includes('chart') && (React.createElement(React.Fragment, null,
|
|
255
|
+
React.createElement("div", { className: "grid grid-cols-2 gap-3" },
|
|
256
|
+
React.createElement("div", null,
|
|
257
|
+
React.createElement("label", { className: "text-xs font-semibold theme-text-secondary uppercase tracking-wider" }, "X-Axis"),
|
|
258
|
+
React.createElement("textarea", { value: xCol, onChange: e => setXCol(e.target.value), className: "w-full theme-input mt-1 font-mono text-sm", rows: 2, placeholder: "e.g., strftime('%Y-%m-%d', timestamp) as date" }),
|
|
259
|
+
outputColumns.length > 0 && (React.createElement("div", { className: "text-xs theme-text-secondary mt-1" },
|
|
260
|
+
"Available: ",
|
|
261
|
+
outputColumns.map(c => c.name).join(', ')))),
|
|
262
|
+
React.createElement("div", null,
|
|
263
|
+
React.createElement("label", { className: "text-xs font-semibold theme-text-secondary uppercase tracking-wider" }, "Y-Axis (comma separated for multi-series)"),
|
|
264
|
+
React.createElement("textarea", { value: yCol, onChange: e => setYCol(e.target.value), className: "w-full theme-input mt-1 font-mono text-sm", rows: 2, placeholder: "e.g., COUNT(*) as count" }))),
|
|
265
|
+
React.createElement("div", { className: "grid grid-cols-2 gap-3" },
|
|
266
|
+
React.createElement("div", null,
|
|
267
|
+
React.createElement("label", { className: "text-xs font-semibold theme-text-secondary uppercase tracking-wider" }, "Chart Type"),
|
|
268
|
+
React.createElement("select", { value: chartType, onChange: e => setChartType(e.target.value), className: "w-full theme-input mt-1" }, CHART_TYPES.map(opt => (React.createElement("option", { key: opt.value, value: opt.value }, opt.label))))),
|
|
269
|
+
React.createElement("div", null,
|
|
270
|
+
React.createElement("label", { className: "text-xs font-semibold theme-text-secondary uppercase tracking-wider" }, "GROUP BY (optional)"),
|
|
271
|
+
React.createElement("input", { type: "text", value: groupBy, onChange: e => setGroupBy(e.target.value), className: "w-full theme-input mt-1 font-mono text-sm", placeholder: "e.g., strftime('%Y-%m-%d', timestamp)" })))))),
|
|
272
|
+
React.createElement("div", { className: "p-3 border theme-border rounded-lg theme-bg-tertiary space-y-3" },
|
|
273
|
+
React.createElement("div", { className: "flex justify-between items-center" },
|
|
274
|
+
React.createElement("h4", { className: "text-sm font-semibold theme-text-primary" }, "Filter Toggles (optional)"),
|
|
275
|
+
React.createElement("button", { onClick: addToggle, className: "text-xs theme-button-subtle" }, "+ Add Toggle")),
|
|
276
|
+
toggleOptions.map((toggle, index) => (React.createElement("div", { key: index, className: "flex gap-2 items-start" },
|
|
277
|
+
React.createElement("div", { className: "flex-1" },
|
|
278
|
+
React.createElement("input", { type: "text", value: toggle.label, onChange: e => handleToggleChange(index, 'label', e.target.value), className: "w-full theme-input text-sm", placeholder: "Label (e.g., 7d)" })),
|
|
279
|
+
React.createElement("div", { className: "flex-[2]" },
|
|
280
|
+
React.createElement("input", { type: "text", value: toggle.modifier, onChange: e => handleToggleChange(index, 'modifier', e.target.value), className: "w-full theme-input text-sm font-mono", placeholder: "WHERE timestamp >= date('now', '-7 days')" })),
|
|
281
|
+
React.createElement("button", { onClick: () => removeToggle(index), className: "p-1 text-red-400 hover:text-red-300" },
|
|
282
|
+
React.createElement(X, { size: 16 }))))))),
|
|
283
|
+
React.createElement("div", { className: "flex justify-end gap-3 mt-6 pt-4 border-t theme-border flex-shrink-0" },
|
|
284
|
+
React.createElement("button", { onClick: onClose, className: "theme-button px-4 py-2 text-sm rounded" }, "Cancel"),
|
|
285
|
+
React.createElement("button", { onClick: handleSave, className: "theme-button-primary px-4 py-2 text-sm rounded" }, isEditMode ? 'Save Changes' : 'Create Widget')))));
|
|
286
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface WidgetGridProps {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
columns?: number | {
|
|
5
|
+
sm?: number;
|
|
6
|
+
md?: number;
|
|
7
|
+
lg?: number;
|
|
8
|
+
xl?: number;
|
|
9
|
+
};
|
|
10
|
+
gap?: number;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const WidgetGrid: React.FC<WidgetGridProps>;
|
|
14
|
+
export interface WidgetSpanProps {
|
|
15
|
+
span?: number;
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const WidgetSpan: React.FC<WidgetSpanProps>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export const WidgetGrid = ({ children, columns = { sm: 1, md: 2, lg: 3, xl: 4 }, gap = 16, className = '' }) => {
|
|
3
|
+
const gridClass = typeof columns === 'number'
|
|
4
|
+
? ''
|
|
5
|
+
: `grid-cols-${columns.sm || 1} md:grid-cols-${columns.md || 2} lg:grid-cols-${columns.lg || 3} xl:grid-cols-${columns.xl || 4}`;
|
|
6
|
+
const gridStyle = typeof columns === 'number'
|
|
7
|
+
? { gridTemplateColumns: `repeat(${columns}, 1fr)`, gap }
|
|
8
|
+
: { gap };
|
|
9
|
+
return (React.createElement("div", { className: `grid ${gridClass} ${className}`, style: gridStyle }, children));
|
|
10
|
+
};
|
|
11
|
+
export const WidgetSpan = ({ span = 1, children, className = '' }) => (React.createElement("div", { className: className, style: { gridColumn: `span ${span}` } }, children));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { Widget, StatValue, StatList, WidgetToggle } from './Widget';
|
|
2
|
+
export type { WidgetProps, StatWidgetProps, StatListProps, WidgetToggleProps } from './Widget';
|
|
3
|
+
export { WidgetGrid, WidgetSpan } from './WidgetGrid';
|
|
4
|
+
export type { WidgetGridProps, WidgetSpanProps } from './WidgetGrid';
|
|
5
|
+
export { QueryWidget } from './QueryWidget';
|
|
6
|
+
export type { QueryWidgetConfig, QueryWidgetProps } from './QueryWidget';
|
|
7
|
+
export { WidgetBuilder } from './WidgetBuilder';
|
|
8
|
+
export type { WidgetBuilderProps, WidgetConfig, SchemaColumn } from './WidgetBuilder';
|