Skip to content

Conversation

@NoOne7135
Copy link
Contributor

…ogic

@NoOne7135 NoOne7135 requested a review from Copilot November 4, 2025 11:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds column sorting functionality to the Table component, supporting both client-side and server-side sorting modes.

  • Adds interactive column headers with visual sort indicators (ascending/descending/unsorted)
  • Implements client-side sorting for array data sources with support for nested field paths, dates, and numbers
  • Extends server-side data fetching to pass sort parameters to the data provider function

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<!-- Unsorted indicator -->
<svg v-if="!isSorted(column)" class="w-3 h-3 ms-1.5 opacity-30" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Zm6.852 1.952H8.574a2.072 2.072 0 0 0-1.847 1.087 1.9 1.9 0 0 0 .11 1.985l3.426 5.05a2.123 2.123 0 0 0 3.472 0l3.427-5.05a1.9 1.9 0 0 0 .11-1.985 2.074 2.074 0 0 0-1.846-1.087Z"></path></svg>
<!-- Sorted ascending indicator -->
<svg v-else-if="currentSortDirection === 'asc'" class="w-3 h-3 ms-1.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 0z"></path></svg>
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SVG path for ascending sort indicator is incomplete. The path ends with '1.847 0z' but should end with '1.086Zm6.852...' to match the complete path from the unsorted indicator. This will result in an incorrect visual representation of the ascending arrow.

Suggested change
<svg v-else-if="currentSortDirection === 'asc'" class="w-3 h-3 ms-1.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 0z"></path></svg>
<svg v-else-if="currentSortDirection === 'asc'" class="w-3 h-3 ms-1.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Z"></path></svg>

Copilot uses AI. Check for mistakes.
<!-- Sorted ascending indicator -->
<svg v-else-if="currentSortDirection === 'asc'" class="w-3 h-3 ms-1.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 0z"></path></svg>
<!-- Sorted descending indicator (rotated) -->
<svg v-else class="w-3 h-3 ms-1.5 rotate-180" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 0z"></path></svg>
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SVG path for descending sort indicator has the same incomplete path issue as the ascending indicator. The path ends with '1.847 0z' but should include the complete path definition to properly render the arrow icon.

Suggested change
<svg v-else class="w-3 h-3 ms-1.5 rotate-180" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 0z"></path></svg>
<svg v-else class="w-3 h-3 ms-1.5 rotate-180" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Zm6.852 1.952H8.574a2.072 2.072 0 0 0-1.847 1.087 1.9 1.9 0 0 0 .11 1.985l3.426 5.05a2.123 2.123 0 0 0 3.472 0l3.427-5.05a1.9 1.9 0 0 0 .11-1.985 2.074 2.074 0 0 0-1.846-1.087Z"></path></svg>

Copilot uses AI. Check for mistakes.
emit('update:sortField', currentSortField.value);
emit('update:sortDirection', currentSortField.value ? currentSortDirection.value : undefined as any);
emit('sort-change', { field: currentSortField.value, direction: currentSortDirection.value });
});
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The watcher triggers on initial component mount when currentSortField is initialized with props.defaultSortField, causing an unnecessary refresh() call after the onMounted hook already calls refresh(). Consider using { immediate: false } option or checking if this is the initial load to avoid duplicate data fetching.

Suggested change
});
}, { immediate: false });

Copilot uses AI. Check for mistakes.
if (currentPage.value !== 1) currentPage.value = 1;
refresh();
emit('update:sortField', currentSortField.value);
emit('update:sortDirection', currentSortField.value ? currentSortDirection.value : undefined as any);
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The as any type assertion bypasses TypeScript's type checking. Consider properly typing the emit parameter or using a union type like 'asc' | 'desc' | undefined in the emit definition instead of forcing the type with as any.

Suggested change
emit('update:sortDirection', currentSortField.value ? currentSortDirection.value : undefined as any);
emit('update:sortDirection', currentSortField.value ? currentSortDirection.value : undefined);

Copilot uses AI. Check for mistakes.
offset: (currentLoadingIndex - 1) * props.pageSize,
limit: props.pageSize,
sortField: currentSortField.value,
sortDirection: currentSortDirection.value,
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When sorting is cleared (sortField is undefined), the function still passes currentSortDirection.value which will be 'asc' by default. This could confuse server-side implementations. Consider only passing sortDirection when sortField has a value, e.g., ...(currentSortField.value ? { sortDirection: currentSortDirection.value } : {}).

Suggested change
sortDirection: currentSortDirection.value,
...(currentSortField.value ? { sortDirection: currentSortDirection.value } : {}),

Copilot uses AI. Check for mistakes.
@NoOne7135 NoOne7135 requested a review from ivictbor November 10, 2025 08:35
{ name: 'Alice', age: 35, country: 'BR' },
{ name: 'Colin', age: 40, country: 'AU' },
]"
:sortable="true"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets remove this and use col.sortable (default = false)


const res = await fetch(url.toString(), { credentials: 'include' });
const json = await res.json();
return { data: json.data, total: json.total };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets simplify

const { data, total } = callAdminForthApi('getProducts', {limit, offset, softField, sortDirection});

// Helper function to get nested properties by path
const getByPath = (o:any, p:string) => p.split('.').reduce((a:any,k)=>a?.[k], o);
return [...data].sort((a,b) => {
let av = getByPath(a, sortField), bv = getByPath(b, sortField);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NoOne7135 should be const?

@NoOne7135 NoOne7135 requested a review from Copilot November 10, 2025 13:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<svg v-else-if="currentSortDirection === 'asc'" class="w-3 h-3 ms-1.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 0z"/></svg>

<!-- Sorted descending -->
<svg v-else class="w-3 h-3 ms-1.5 rotate-180" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 0z"/></svg>
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SVG path for the descending sort icon is incomplete. It ends with 1.847 0z but should have a complete path. This matches the same truncated path as line 26, resulting in an incorrect/incomplete icon rendering.

Suggested change
<svg v-else class="w-3 h-3 ms-1.5 rotate-180" fill="currentColor" viewBox="0 0 24 24"><path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 0z"/></svg>
<svg v-else class="w-3 h-3 ms-1.5" fill="currentColor" viewBox="0 0 24 24"><path d="M15.426 12.976H8.574a2.075 2.075 0 0 1-1.847 1.086 1.9 1.9 0 0 1 .11 1.986l3.427 5.05a2.122 2.122 0 0 0 3.472 0l3.426-5.05a1.9 1.9 0 0 1 .11-1.986 2.075 2.075 0 0 1-1.846-1.086z"/></svg>

Copilot uses AI. Check for mistakes.
Comment on lines 213 to 219
watch([() => currentSortField.value, () => currentSortDirection.value], () => {
if (currentPage.value !== 1) currentPage.value = 1;
refresh();
emit('update:sortField', currentSortField.value);
emit('update:sortDirection', currentSortField.value ? currentSortDirection.value : undefined);
emit('sort-change', { field: currentSortField.value, direction: currentSortDirection.value });
});
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This watcher triggers on mount when refs are initialized (even if values don't change), causing refresh() to be called twice: once in onMounted (line 197) and once from this watcher. Consider using { immediate: false } or restructuring to avoid the duplicate data fetch on initial load.

Copilot uses AI. Check for mistakes.
Comment on lines +313 to +316
currentSortDirection.value =
currentSortDirection.value === 'asc' ? 'desc' :
currentSortField.value ? (currentSortField.value = undefined, props.defaultSortDirection ?? 'asc') :
'asc';
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This tri-state toggle logic uses the comma operator to both clear currentSortField.value and return defaultSortDirection, which is difficult to read and reason about. Consider using an if-else block or separate the side effect from the assignment for better clarity.

Suggested change
currentSortDirection.value =
currentSortDirection.value === 'asc' ? 'desc' :
currentSortField.value ? (currentSortField.value = undefined, props.defaultSortDirection ?? 'asc') :
'asc';
if (currentSortDirection.value === 'asc') {
currentSortDirection.value = 'desc';
} else if (currentSortField.value) {
currentSortField.value = undefined;
currentSortDirection.value = props.defaultSortDirection ?? 'asc';
} else {
currentSortDirection.value = 'asc';
}

Copilot uses AI. Check for mistakes.
const av = getByPath(a, sortField), bv = getByPath(b, sortField);
// Handle null/undefined values
if (av == null && bv == null) return 0;
// Handle null/undefined values
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate comment 'Handle null/undefined values' appears on consecutive lines. Remove the duplicate on line 336 to improve code clarity.

Suggested change
// Handle null/undefined values

Copilot uses AI. Check for mistakes.
Table supports column sorting out of the box.

- By default, columns are NOT sortable. Enable sorting per column with `sortable: true`.
- Clicking a sortable header cycles sorting in a tri‑state order:
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a non-standard hyphen character (U+2011 non-breaking hyphen) instead of a standard hyphen. Use 'tri-state' with a regular hyphen for consistency.

Suggested change
- Clicking a sortable header cycles sorting in a tristate order:
- Clicking a sortable header cycles sorting in a tri-state order:

Copilot uses AI. Check for mistakes.
@NoOne7135 NoOne7135 merged commit f11fd8c into next Nov 10, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants