Skip to content
Draft
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
29 changes: 27 additions & 2 deletions components/task-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Checkbox } from "@/components/ui/checkbox"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { MoreHorizontal, Clock, Edit, Trash2 } from "lucide-react"
import { Input } from "@/components/ui/input"
import { MoreHorizontal, Clock, Edit, Trash2, Search } from "lucide-react"
import { deleteTask, updateTaskStatus } from "@/app/(dashboard)/tasks/actions"
import { formatDateForDisplay } from "@/lib/date-utils"
import { EditTaskForm } from "./edit-task-form"
Expand All @@ -22,6 +23,7 @@ type TaskWithProfile = PrismaTask & {

export function TaskList({ initialTasks }: { initialTasks: TaskWithProfile[]; }) {
const [tasks, setTasks] = useState(initialTasks)
const [searchTerm, setSearchTerm] = useState("")
const [optimisticTasks, setOptimisticTasks] = useOptimistic(
tasks,
(state, { action, task }: { action: "delete" | "toggle"; task: TaskWithProfile | { id: number } }) => {
Expand Down Expand Up @@ -75,9 +77,32 @@ export function TaskList({ initialTasks }: { initialTasks: TaskWithProfile[]; })
.toUpperCase()
}

// Filter tasks based on search term
const filteredTasks = optimisticTasks.filter((task) => {
if (!searchTerm) return true
const lowerSearchTerm = searchTerm.toLowerCase()
return (
task.name.toLowerCase().includes(lowerSearchTerm) ||
(task.description && task.description.toLowerCase().includes(lowerSearchTerm))
)
})

return (
<div className="space-y-4">
{optimisticTasks.map((task) => (
{/* Search Input */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="text"
placeholder="Search tasks by title or description..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>

{/* Task List */}
{filteredTasks.map((task) => (
<Dialog key={task.id} open={openDialogs[task.id]} onOpenChange={(open) =>
setOpenDialogs(prev => ({ ...prev, [task.id]: open }))
}>
Expand Down
3 changes: 2 additions & 1 deletion jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ module.exports = {
transformIgnorePatterns: ['/node_modules/(?!(.*?))'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
'\\.(css|less|sass|scss)$': 'identity-obj-proxy'
'\\.(css|less|sass|scss)$': 'identity-obj-proxy',
'^next/font/google$': '<rootDir>/tests/__mocks__/nextFontMock.js'
}
,
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
Expand Down
35 changes: 13 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions tests/__mocks__/nextFontMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Mock for next/font/google
module.exports = {
Poppins: () => ({
className: 'mocked-poppins',
}),
Inter: () => ({
className: 'mocked-inter',
}),
};
2 changes: 1 addition & 1 deletion tests/e2e/kanban.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test'
// import { test, expect } from '@playwright/test'

// test.describe('Kanban drag/drop', () => {
// test('drags a card fully into another column', async ({ page }) => {
Expand Down
33 changes: 33 additions & 0 deletions tests/e2e/tasks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,39 @@ test.describe('Task CRUD flows', () => {
await expect(page.locator('h3', { hasText: title })).toBeVisible();
});

test('search tasks by title', async ({ page }) => {
// Navigate to tasks page
await page.goto('/tasks');

// Wait for tasks to load
await page.waitForSelector('[data-testid^="task-card-"]', { timeout: 5000 });

// Get the search input
const searchInput = page.locator('input[placeholder*="Search tasks"]');
await expect(searchInput).toBeVisible();

// Count initial tasks
const initialTaskCount = await page.locator('[data-testid^="task-card-"]').count();

// Search for a specific task (assuming there's a task with "Design" in the seeded data)
await searchInput.fill('Design');

// Wait a bit for the filter to apply
await page.waitForTimeout(500);

// Count filtered tasks - should be less than or equal to initial count
const filteredTaskCount = await page.locator('[data-testid^="task-card-"]').count();
expect(filteredTaskCount).toBeLessThanOrEqual(initialTaskCount);

// Clear search
await searchInput.clear();
await page.waitForTimeout(500);

// Verify all tasks are visible again
const clearedTaskCount = await page.locator('[data-testid^="task-card-"]').count();
expect(clearedTaskCount).toBe(initialTaskCount);
});

// test('delete task', async ({ page }) => {
// const title = await createTaskViaUI(page, 'E2E Delete');

Expand Down
Loading