@@ -541,6 +542,13 @@ function SyncHistory({ logs, isLoading }: SyncHistoryProps) {
-{log.docsDeleted}
>
)}
+ {log.docsFailed > 0 && (
+ <>
+ {(log.docsAdded > 0 || log.docsUpdated > 0 || log.docsDeleted > 0) &&
+ ' '}
+
!{log.docsFailed}
+ >
+ )}
>
) : (
'No changes'
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/table.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/table.tsx
index 4d6ae757faa..bfea42514b7 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/table.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table/table.tsx
@@ -266,6 +266,9 @@ export function Table({
normalizedSelection.startCol === 0 &&
normalizedSelection.endCol === columns.length - 1
+ const isAllRowsSelectedRef = useRef(isAllRowsSelected)
+ isAllRowsSelectedRef.current = isAllRowsSelected
+
const columnsRef = useRef(columns)
const rowsRef = useRef(rows)
const selectionAnchorRef = useRef(selectionAnchor)
@@ -541,6 +544,14 @@ export function Table({
scrollRef.current?.focus({ preventScroll: true })
}, [])
+ const handleSelectAllToggle = useCallback(() => {
+ if (isAllRowsSelectedRef.current) {
+ handleClearSelection()
+ } else {
+ handleSelectAllRows()
+ }
+ }, [handleClearSelection, handleSelectAllRows])
+
const handleColumnResizeStart = useCallback((columnName: string) => {
setResizingColumn(columnName)
}, [])
@@ -1075,6 +1086,11 @@ export function Table({
updateColumnMutation.mutate({ columnName, updates: { unique: !previousValue } })
}, [])
+ const handleRenameColumn = useCallback(
+ (name: string) => columnRename.startRename(name, name),
+ [columnRename.startRename]
+ )
+
const handleDeleteColumn = useCallback((columnName: string) => {
setDeletingColumn(columnName)
}, [])
@@ -1107,6 +1123,78 @@ export function Table({
[columns]
)
+ const tableDataRef = useRef(tableData)
+ tableDataRef.current = tableData
+
+ const handleStartTableRename = useCallback(() => {
+ const data = tableDataRef.current
+ if (data) tableHeaderRename.startRename(tableId, data.name)
+ }, [tableHeaderRename.startRename, tableId])
+
+ const handleShowDeleteTableConfirm = useCallback(() => {
+ setShowDeleteTableConfirm(true)
+ }, [])
+
+ const hasTableData = !!tableData
+
+ const breadcrumbs = useMemo(
+ () => [
+ { label: 'Tables', onClick: handleNavigateBack },
+ {
+ label: tableData?.name ?? '',
+ editing: tableHeaderRename.editingId
+ ? {
+ isEditing: true,
+ value: tableHeaderRename.editValue,
+ onChange: tableHeaderRename.setEditValue,
+ onSubmit: tableHeaderRename.submitRename,
+ onCancel: tableHeaderRename.cancelRename,
+ }
+ : undefined,
+ dropdownItems: [
+ {
+ label: 'Rename',
+ icon: Pencil,
+ disabled: !hasTableData,
+ onClick: handleStartTableRename,
+ },
+ {
+ label: 'Delete',
+ icon: Trash,
+ disabled: !hasTableData,
+ onClick: handleShowDeleteTableConfirm,
+ },
+ ],
+ },
+ ],
+ [
+ handleNavigateBack,
+ tableData?.name,
+ tableHeaderRename.editingId,
+ tableHeaderRename.editValue,
+ tableHeaderRename.setEditValue,
+ tableHeaderRename.submitRename,
+ tableHeaderRename.cancelRename,
+ hasTableData,
+ handleStartTableRename,
+ handleShowDeleteTableConfirm,
+ ]
+ )
+
+ const createAction = useMemo(
+ () => ({
+ label: 'New column',
+ onClick: handleAddColumn,
+ disabled: addColumnMutation.isPending,
+ }),
+ [handleAddColumn, addColumnMutation.isPending]
+ )
+
+ const filterElement = useMemo(
+ () =>
,
+ [columns, handleFilterApply]
+ )
+
const activeSortState = useMemo(() => {
if (!queryOptions.sort) return null
const entries = Object.entries(queryOptions.sort)
@@ -1156,50 +1244,9 @@ export function Table({
{!embedded && (
<>
-
{
- if (tableData) tableHeaderRename.startRename(tableId, tableData.name)
- },
- },
- {
- label: 'Delete',
- icon: Trash,
- disabled: !tableData,
- onClick: () => setShowDeleteTableConfirm(true),
- },
- ],
- },
- ]}
- create={{
- label: 'New column',
- onClick: handleAddColumn,
- disabled: addColumnMutation.isPending,
- }}
- />
+
- }
- />
+
>
)}
@@ -1253,21 +1300,10 @@ export function Table({
) : (
- |
-
- {
- if (isAllRowsSelected) {
- handleClearSelection()
- } else {
- handleSelectAllRows()
- }
- }}
- />
-
- |
+
{columns.map((column) => (
columnRename.startRename(name, name)}
+ onRenameColumn={handleRenameColumn}
onChangeType={handleChangeType}
onInsertLeft={handleInsertColumnLeft}
onInsertRight={handleInsertColumnRight}
@@ -1290,19 +1326,10 @@ export function Table({
onResizeEnd={handleColumnResizeEnd}
/>
))}
-
-
- |
+
)}
@@ -1360,20 +1387,7 @@ export function Table({
)
})}
{userPermissions.canEdit && (
-
- |
-
- |
-
+
)}
>
)}
@@ -2326,6 +2340,67 @@ const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
)
})
+const SelectAllCheckbox = React.memo(function SelectAllCheckbox({
+ checked,
+ onCheckedChange,
+}: {
+ checked: boolean
+ onCheckedChange: () => void
+}) {
+ return (
+
+
+
+
+ |
+ )
+})
+
+const AddColumnButton = React.memo(function AddColumnButton({
+ onClick,
+ disabled,
+}: {
+ onClick: () => void
+ disabled: boolean
+}) {
+ return (
+
+
+ |
+ )
+})
+
+const AddRowButton = React.memo(function AddRowButton({
+ colSpan,
+ onClick,
+}: {
+ colSpan: number
+ onClick: () => void
+}) {
+ return (
+
+ |
+
+ |
+
+ )
+})
+
function ColumnTypeIcon({ type }: { type: string }) {
const Icon = COLUMN_TYPE_ICONS[type] ?? TypeText
return
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx
index d5f7f03741f..4f082a0e978 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx
@@ -360,7 +360,7 @@ export function SearchModal({
'-translate-x-1/2 fixed top-[15%] z-50 w-[500px] overflow-hidden rounded-[12px] border border-[var(--border)] bg-[var(--surface-4)] shadow-lg',
open ? 'visible opacity-100' : 'invisible opacity-0'
)}
- style={{ left: 'calc(50% + var(--sidebar-width, 0px) / 2)' }}
+ style={{ left: '50%' }}
>
state.folders)
const { getFolderTree, expandedFolders, getFolderPath, setExpanded } = useFolderStore()
@@ -360,10 +358,7 @@ export function WorkflowList({
folderDescendantIds,
})
- const isWorkflowActive = useCallback(
- (wfId: string) => pathname === `/workspace/${workspaceId}/w/${wfId}`,
- [pathname, workspaceId]
- )
+ const isWorkflowActive = useCallback((wfId: string) => wfId === workflowId, [workflowId])
useEffect(() => {
if (!workflowId || isLoading || foldersLoading) return
@@ -646,4 +641,4 @@ export function WorkflowList({
)}
)
-}
+})
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx
index 4ce5e028324..52c17f5eee4 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx
@@ -77,6 +77,107 @@ function SidebarItemSkeleton() {
)
}
+const SidebarTaskItem = memo(function SidebarTaskItem({
+ task,
+ active,
+ isSelected,
+ onMultiSelectClick,
+ onContextMenu,
+}: {
+ task: { id: string; href: string; name: string }
+ active: boolean
+ isSelected: boolean
+ onMultiSelectClick: (taskId: string, shiftKey: boolean, metaKey: boolean) => void
+ onContextMenu: (e: React.MouseEvent, taskId: string) => void
+}) {
+ return (
+ {
+ if (task.id === 'new') return
+ if (e.shiftKey || e.metaKey || e.ctrlKey) {
+ e.preventDefault()
+ onMultiSelectClick(task.id, e.shiftKey, e.metaKey || e.ctrlKey)
+ } else {
+ useFolderStore.getState().clearTaskSelection()
+ }
+ }}
+ onContextMenu={task.id !== 'new' ? (e) => onContextMenu(e, task.id) : undefined}
+ >
+
+
+ {task.name}
+
+
+ )
+})
+
+interface SidebarNavItemData {
+ id: string
+ label: string
+ icon: React.ComponentType<{ className?: string }>
+ href?: string
+ onClick?: () => void
+}
+
+const SidebarNavItem = memo(function SidebarNavItem({
+ item,
+ active,
+ showCollapsedContent,
+ onContextMenu,
+}: {
+ item: SidebarNavItemData
+ active: boolean
+ showCollapsedContent: boolean
+ onContextMenu?: (e: React.MouseEvent, href: string) => void
+}) {
+ const Icon = item.icon
+ const baseClasses =
+ 'group flex h-[30px] items-center gap-[8px] rounded-[8px] mx-[2px] px-[8px] text-[14px] hover:bg-[var(--surface-active)]'
+ const activeClasses = active ? 'bg-[var(--surface-active)]' : ''
+
+ const element = item.onClick ? (
+
+ ) : (
+ onContextMenu(e, item.href!) : undefined}
+ >
+
+
+ {item.label}
+
+
+ )
+
+ return (
+
+ {element}
+ {showCollapsedContent && (
+
+ {item.label}
+
+ )}
+
+ )
+})
+
/** Event name for sidebar scroll operations - centralized for consistency */
export const SIDEBAR_SCROLL_EVENT = 'sidebar-scroll-to-item'
@@ -823,50 +924,15 @@ export const Sidebar = memo(function Sidebar() {
<>
{/* Top Navigation: Home, Search */}
- {topNavItems.map((item) => {
- const Icon = item.icon
- const active = item.href ? pathname?.startsWith(item.href) : false
- const baseClasses =
- 'group flex h-[30px] items-center gap-[8px] rounded-[8px] mx-[2px] px-[8px] text-[14px] hover:bg-[var(--surface-active)]'
- const activeClasses = active ? 'bg-[var(--surface-active)]' : ''
-
- const element = item.onClick ? (
-
- ) : (
-
handleNavItemContextMenu(e, item.href!)}
- >
-
-
- {item.label}
-
-
- )
-
- return (
-
- {element}
- {showCollapsedContent && (
-
- {item.label}
-
- )}
-
- )
- })}
+ {topNavItems.map((item) => (
+
+ ))}
{/* Workspace */}
@@ -879,36 +945,15 @@ export const Sidebar = memo(function Sidebar() {