Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ SPDX-License-Identifier: Apache-2.0

Modern React-based UI for OSMO resource management. Built with Next.js 16, React 19, and Tailwind CSS 4.

**Requirements:** Node.js >= 22 (see `.nvmrc`), pnpm 10+ (see `packageManager` in `package.json`)

## Quick Start

```bash
Expand Down
10 changes: 5 additions & 5 deletions src/ui/src/components/data-table/create-column-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ interface ColumnConfigInput<TColumnId extends string> {
sizeConfig: ColumnSizeConfig[];
/** Optional columns that can be toggled in the visibility menu */
optionalColumns?: ColumnDefinition[];
/** Default sort configuration */
defaultSort?: { column: TColumnId; direction: "asc" | "desc" };
/** Default sort configuration (null = no default sort) */
defaultSort?: { column: TColumnId; direction: "asc" | "desc" } | null;
/** Default panel width percentage (if feature uses panels) */
defaultPanelWidth?: number;
}
Expand All @@ -83,8 +83,8 @@ export interface ColumnConfig<TColumnId extends string> {
COLUMN_SIZE_CONFIG: readonly ColumnSizeConfig[];
/** Optional columns that can be toggled */
OPTIONAL_COLUMNS: readonly ColumnDefinition[];
/** Default sort configuration */
DEFAULT_SORT: { column: TColumnId; direction: "asc" | "desc" };
/** Default sort configuration (null = no default sort) */
DEFAULT_SORT: { column: TColumnId; direction: "asc" | "desc" } | null;
/** Default panel width percentage */
DEFAULT_PANEL_WIDTH: number;
}
Expand Down Expand Up @@ -130,7 +130,7 @@ export function createColumnConfig<TColumnId extends string>(
DEFAULT_COLUMN_ORDER: Object.freeze([...config.defaultOrder]),
COLUMN_SIZE_CONFIG: Object.freeze([...config.sizeConfig]),
OPTIONAL_COLUMNS: Object.freeze(optionalColumns),
DEFAULT_SORT: config.defaultSort ?? { column: config.columns[0], direction: "asc" as const },
DEFAULT_SORT: config.defaultSort === null ? null : (config.defaultSort ?? { column: config.columns[0], direction: "asc" as const }),
DEFAULT_PANEL_WIDTH: config.defaultPanelWidth ?? 40,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,16 @@ export const GroupNode = memo(function GroupNode({ data }: GroupNodeProps) {
const scrollContainerRef = useRef<HTMLDivElement>(null);

// Memoize tasks array with natural sort order
const tasks = useMemo(() => [...(group.tasks || [])].sort((a, b) => naturalCompare(a.name, b.name)), [group.tasks]);
const tasks = useMemo(
() =>
[...(group.tasks || [])].sort((a, b) => {
if (a.lead && !b.lead) return -1;
if (!a.lead && b.lead) return 1;
return naturalCompare(a.name, b.name);
}),
[group.tasks],
);
// const tasks = group.tasks || [];
const totalCount = tasks.length;
const isSingleTask = totalCount === 1;
const hasManyTasks = totalCount > 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ const taskColumnConfig = createColumnConfig<TaskColumnId>({
{ id: "endTime", label: "End", menuLabel: "End Time" },
{ id: "retry", label: "Retry", menuLabel: "Retry ID" },
],
defaultSort: { column: "name", direction: "asc" },
defaultSort: null,
});

// =============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,14 @@ export const WorkflowTasksTable = memo(function WorkflowTasksTable({
}

// Step 2: Apply sorting to filtered tasks
const sortedTasks = sortComparator ? [...filteredTasks].sort(sortComparator) : filteredTasks;
// Default: lead task first, then alphabetical by name
const sortedTasks = sortComparator
? [...filteredTasks].sort(sortComparator)
: [...filteredTasks].sort((a, b) => {
if (a.lead && !b.lead) return -1;
if (!a.lead && b.lead) return 1;
return naturalCompare(a.name, b.name);
});

// Step 3: Calculate position on FINAL visible list
const visibleTasks: TaskWithDuration[] = sortedTasks.map((task, index) => ({
Expand Down
Loading