Skip to content

Commit

Permalink
Fix Document Sorting (#235)
Browse files Browse the repository at this point in the history
Co-authored-by: Andre Vitorio <andre@vitorio.net>
  • Loading branch information
amponce and avitorio authored Jun 25, 2024
1 parent c3495a4 commit 20993ce
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 26 deletions.
99 changes: 84 additions & 15 deletions packages/outstatic/src/components/DocumentsTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@ import { sentenceCase } from 'change-case'
import cookies from 'js-cookie'
import { Settings } from 'lucide-react'
import Link from 'next/link'
import { useState } from 'react'
import { useState, useCallback, useEffect } from 'react'
import { Button } from '../ui/button'
import {
CaretSortIcon,
CaretDownIcon,
CaretUpIcon
} from '@radix-ui/react-icons'
import {
useSortedDocuments,
SortConfig
} from '@/utils/hooks/useSortedDocuments'

type DocumentsTableProps = {
documents: OstDocument[]
Expand Down Expand Up @@ -38,20 +47,74 @@ const DocumentsTable = (props: DocumentsTableProps) => {
)
const [showColumnOptions, setShowColumnOptions] = useState(false)

const [sortConfig, setSortConfig] = useState<SortConfig>({
key: 'publishedAt',
direction: 'descending'
})

const sortedDocuments = useSortedDocuments(documents, sortConfig)

const requestSort = useCallback((key: keyof OstDocument) => {
setSortConfig((prevConfig) => ({
key,
direction:
prevConfig.key === key && prevConfig.direction === 'ascending'
? 'descending'
: 'ascending'
}))
}, [])

const handleDelete = useCallback((slug: string) => {
setDocuments((prevDocuments) =>
prevDocuments.filter((doc) => doc.slug !== slug)
)
}, [])

useEffect(() => {
setDocuments(props.documents)
}, [props.documents])

return (
<div>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm text-gray-500">
<thead className="bg-gray-50 text-xs uppercase text-gray-700 border-b">
<tr>
{columns.map((column) => (
<th key={column.value} scope="col" className="px-6 py-3">
{column.label}
<th
key={column.value}
scope="col"
className="px-6 py-3 cursor-pointer"
onClick={() => requestSort(column.value)}
>
<div className="flex items-center">
<span>{column.label}</span>
<span
className="ml-2"
data-testid={`sort-icon-${column.value}`}
>
{sortConfig.key === column.value ? (
sortConfig.direction === 'ascending' ? (
<CaretUpIcon
className="h-4 w-4"
data-testid="caret-up-icon"
/>
) : (
<CaretDownIcon
className="h-4 w-4"
data-testid="caret-down-icon"
/>
)
) : (
<CaretSortIcon
className="h-4 w-4"
data-testid="caret-sort-icon"
/>
)}
</span>
</div>
</th>
))}
<th
scope="col"
className="px-6 py-2 text-right flex justify-end items-center"
>
<th scope="col" className="px-6 py-3 text-right">
<Button
variant="ghost"
size="icon"
Expand All @@ -64,8 +127,8 @@ const DocumentsTable = (props: DocumentsTableProps) => {
</tr>
</thead>
<tbody>
{documents &&
documents.map((document) => (
{sortedDocuments &&
sortedDocuments.map((document) => (
<tr
key={document.slug}
className="border-b bg-white hover:bg-gray-50"
Expand All @@ -77,11 +140,7 @@ const DocumentsTable = (props: DocumentsTableProps) => {
<DeleteDocumentButton
slug={document.slug}
disabled={false}
onComplete={() =>
setDocuments(
documents.filter((p) => p.slug !== document.slug)
)
}
onComplete={() => handleDelete(document.slug)}
collection={props.collection}
/>
</td>
Expand Down Expand Up @@ -135,6 +194,16 @@ const cellSwitch = (
</Link>
</th>
)
case 'status':
return (
<td
key="status"
className="px-6 py-4 text-base font-semibold text-gray-900"
data-testid="status-cell"
>
{item}
</td>
)
default:
return (
<td
Expand Down
75 changes: 64 additions & 11 deletions packages/outstatic/src/components/DocumentsTable/test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { OstDocument } from '@/types/public'
import { act, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import React from 'react'
import DocumentsTable from './'

Expand Down Expand Up @@ -68,17 +67,71 @@ describe('DocumentsTable', () => {
it('removes a document from the table when the Delete button is clicked', async () => {
render(<DocumentsTable documents={mockDocuments} collection={collection} />)

expect(screen.getByText('Document 1')).toBeInTheDocument()

const deleteButtons = screen.getAllByTestId('delete-button')
expect(deleteButtons.length).toEqual(2)

await act(async () => {
await userEvent.click(deleteButtons[0])
})

// Click the delete button for the first document
fireEvent.click(screen.getAllByTestId('delete-button')[0])
fireEvent.click(screen.getByText('Delete'))
await waitFor(() => {
expect(screen.queryByText('Document 1')).toBeNull()
})
})

it('sorts the table by title in ascending and descending order', () => {
render(<DocumentsTable documents={mockDocuments} collection={collection} />)

// Click the title column header to sort ascending
fireEvent.click(screen.getByText('Title'))

const sortedAsc = screen
.getAllByText(/Document/)
.map((node) => node.textContent)
expect(sortedAsc).toEqual(['Document 1', 'Document 2'])

// Click the title column header again to sort descending
fireEvent.click(screen.getByText('Title'))

const sortedDesc = screen
.getAllByText(/Document/)
.map((node) => node.textContent)
expect(sortedDesc).toEqual(['Document 2', 'Document 1'])
})

it('sorts the table by status in ascending and descending order', () => {
render(<DocumentsTable documents={mockDocuments} collection={collection} />)

// Click the status column header to sort ascending
fireEvent.click(screen.getByText('Status'))

const sortedAsc = screen
.getAllByTestId('status-cell')
.map((node) => node.textContent.trim())
expect(sortedAsc).toEqual(['draft', 'published'])

// Click the status column header again to sort descending
fireEvent.click(screen.getByText('Status'))

const sortedDesc = screen
.getAllByTestId('status-cell')
.map((node) => node.textContent.trim())
expect(sortedDesc).toEqual(['published', 'draft'])
})

it('sorts the table by publishedAt in ascending and descending order', () => {
render(<DocumentsTable documents={mockDocuments} collection={collection} />)

// Click the publishedAt column header to sort ascending
fireEvent.click(screen.getByText('Published at'))

const sortedAsc = screen
.getAllByText(/July|August/)
.map((node) => node.textContent)
expect(sortedAsc).toEqual([date1, date2])

// Click the publishedAt column header again to sort descending
fireEvent.click(screen.getByText('Published at'))

const sortedDesc = screen
.getAllByText(/July|August/)
.map((node) => node.textContent)
expect(sortedDesc).toEqual([date2, date1])
})
})
50 changes: 50 additions & 0 deletions packages/outstatic/src/utils/hooks/useSortedDocuments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useMemo, useCallback } from 'react'
import { OstDocument } from '@/types/public'

type SortDirection = 'ascending' | 'descending'

type SortConfig = {
key: keyof OstDocument
direction: SortDirection
}

export const useSortedDocuments = (
documents: OstDocument[],
sortConfig: SortConfig
) => {
const sortFunction = useCallback(
(a: OstDocument, b: OstDocument): number => {
const { key, direction } = sortConfig
const multiplier = direction === 'ascending' ? 1 : -1

if (key === 'publishedAt') {
const dateA = a[key] ? new Date(a[key]).getTime() : 0
const dateB = b[key] ? new Date(b[key]).getTime() : 0
return (dateA - dateB) * multiplier
}

if (key === 'status') {
if (a.status === b.status) {
return a.title.localeCompare(b.title) * multiplier
}
return (a.status === 'published' ? 1 : -1) * multiplier // Fix the sorting logic here
}

const valueA = a[key]
const valueB = b[key]

if (typeof valueA === 'string' && typeof valueB === 'string') {
return valueA.localeCompare(valueB) * multiplier
}

if (valueA < valueB) return -1 * multiplier
if (valueA > valueB) return 1 * multiplier
return 0
},
[sortConfig]
)

return useMemo(() => {
return [...documents].sort(sortFunction)
}, [documents, sortFunction])
}

0 comments on commit 20993ce

Please sign in to comment.