From 54422b6c3ac632988a1eb4d954fa5e52a2bd092c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:04:17 +0000 Subject: [PATCH 1/3] Initial plan From 62743c4ad5ade13735c8e20bcfe3799efb55eec5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:09:26 +0000 Subject: [PATCH 2/3] feat: add task filtering by status and priority (issue #4) Agent-Logs-Url: https://github.com/CanarysPlayground/TaskManagementApplication/sessions/34e3e125-3aaf-42ab-a04f-b068d1d90c6f Co-authored-by: kavyashri-as <213833080+kavyashri-as@users.noreply.github.com> --- dist/server.js | 69 ++++++++++++++++++ dist/src/models/task.js | 2 + dist/src/services/taskSeed.js | 120 +++++++++++++++++++++++++++++++ dist/src/services/taskService.js | 85 ++++++++++++++++++++++ server.ts | 28 ++++++-- src/hooks/useTasks.ts | 11 +-- src/pages/TasksPage.tsx | 35 ++++++++- src/services/taskApiService.ts | 17 ++++- src/services/taskService.ts | 35 ++++++++- src/styles/tasks.css | 25 ++++++- 10 files changed, 412 insertions(+), 15 deletions(-) create mode 100644 dist/server.js create mode 100644 dist/src/models/task.js create mode 100644 dist/src/services/taskSeed.js create mode 100644 dist/src/services/taskService.js diff --git a/dist/server.js b/dist/server.js new file mode 100644 index 0000000..83235dc --- /dev/null +++ b/dist/server.js @@ -0,0 +1,69 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const child_process_1 = require("child_process"); +const taskService_1 = require("./src/services/taskService"); +const validStatuses = ['pending', 'completed']; +// --- App setup --- +const app = (0, express_1.default)(); +app.use(express_1.default.json()); +// GET /tasks — return tasks, optionally filtered by ?status= and/or ?priority= +app.get('/tasks', (req, res) => { + const { status, priority } = req.query; + if (status !== undefined && !validStatuses.includes(status)) { + res.status(400).json({ error: `status must be one of: ${validStatuses.join(', ')}.` }); + return; + } + if (priority !== undefined && !taskService_1.validPriorities.includes(priority)) { + res.status(400).json({ error: `priority must be one of: ${taskService_1.validPriorities.join(', ')}.` }); + return; + } + const tasks = (0, taskService_1.getFilteredTasks)({ + status: status, + priority: priority, + }); + res.status(200).json({ + count: tasks.length, + tasks, + }); +}); +// POST /tasks — create a new task dynamically +app.post('/tasks', (req, res) => { + const { title, priority = 'Medium' } = req.body; + if (!title || typeof title !== 'string' || title.trim() === '') { + res.status(400).json({ error: 'title is required and must be a non-empty string.' }); + return; + } + if (!taskService_1.validPriorities.includes(priority)) { + res.status(400).json({ error: `priority must be one of: ${taskService_1.validPriorities.join(', ')}.` }); + return; + } + const newTask = (0, taskService_1.createTask)({ title, priority }); + res.status(201).json(newTask); +}); +// --- Start server --- +const PORT = process.env.PORT ?? 3001; +function startServer() { + const server = app.listen(PORT, () => { + console.log(`Task API running on http://localhost:${PORT}`); + console.log(' GET /tasks – list all tasks'); + console.log(' POST /tasks – create a task { title, priority? }'); + }); + server.on('error', (err) => { + if (err.code === 'EADDRINUSE') { + console.log(`Port ${PORT} is busy. Killing existing process and retrying...`); + try { + (0, child_process_1.execSync)(`powershell -Command "Get-Process -Id (Get-NetTCPConnection -LocalPort ${PORT} -ErrorAction SilentlyContinue).OwningProcess -ErrorAction SilentlyContinue | Stop-Process -Force"`, { stdio: 'ignore' }); + setTimeout(startServer, 500); + } + catch { + console.error(`Could not free port ${PORT}. Kill it manually and try again.`); + process.exit(1); + } + } + }); +} +startServer(); diff --git a/dist/src/models/task.js b/dist/src/models/task.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/src/models/task.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/services/taskSeed.js b/dist/src/services/taskSeed.js new file mode 100644 index 0000000..31fbfbc --- /dev/null +++ b/dist/src/services/taskSeed.js @@ -0,0 +1,120 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.seedTasks = void 0; +const uuid_1 = require("uuid"); +const priorities = ['Low', 'Medium', 'High']; +const statuses = ['pending', 'completed']; +const seedTitles = [ + 'Set up project repository', + 'Define initial requirements', + 'Design database schema', + 'Configure CI/CD pipeline', + 'Write API specification', + 'Implement user authentication', + 'Build task creation endpoint', + 'Build task listing endpoint', + 'Build task update endpoint', + 'Build task delete endpoint', + 'Add input validation to API', + 'Write unit tests for taskService', + 'Write integration tests for routes', + 'Set up ESLint and Prettier', + 'Configure TypeScript strict mode', + 'Create React project scaffold', + 'Build TaskItem component', + 'Build TaskList component', + 'Build TaskForm component', + 'Wire TaskForm to POST /tasks', + 'Wire TaskList to GET /tasks', + 'Add loading state to TaskList', + 'Add error state to TaskList', + 'Add empty state to TaskList', + 'Extract useTasks custom hook', + 'Extract taskApiService module', + 'Add priority badge styles', + 'Add status indicator styles', + 'Make UI responsive for mobile', + 'Add Refresh button to TasksPage', + 'Implement task filtering by status', + 'Implement task filtering by priority', + 'Add sort by creation date', + 'Add sort by priority', + 'Persist tasks to local storage', + 'Load tasks from local storage on startup', + 'Clear completed tasks feature', + 'Mark all tasks as complete feature', + 'Add task count summary header', + 'Write JSDoc for taskService', + 'Write JSDoc for taskApiService', + 'Write JSDoc for useTasks hook', + 'Document API endpoints in README', + 'Fix CORS configuration on API', + 'Add rate limiting middleware', + 'Set up environment variables', + 'Add health check endpoint', + 'Review and refactor TaskItem', + 'Review and refactor TaskList', + 'Review and refactor TasksPage', + 'Validate TypeScript across client', + 'Validate TypeScript across server', + 'Fix moduleResolution for Vite config', + 'Split tsconfig for client and server', + 'Add Vite proxy config for API', + 'Create custom agent for Task App', + 'Create ui-review-skill', + 'Create task-ui prompt file', + 'Review separation of concerns in components', + 'Extract TaskListLoading component', + 'Extract TaskListError component', + 'Extract TaskListEmpty component', + 'Rename tick to refreshKey in useTasks', + 'Add CSS import to sub-components', + 'Add btn class to Retry button', + 'Extract isCompleted const in TaskItem', + 'Add seed data for demo purposes', + 'Verify 100 tasks load on startup', + 'Test POST /tasks with new seed data', + 'Test GET /tasks returns all seeded tasks', + 'Review priority distribution in seed data', + 'Review status distribution in seed data', + 'Add createdAt timestamps to seed tasks', + 'Confirm seed data matches Task model', + 'Review task titles for clarity', + 'Write end-to-end test for task flow', + 'Add task detail view page', + 'Add task edit modal', + 'Add delete confirmation dialog', + 'Improve error messages in API', + 'Improve empty-state copy in UI', + 'Add keyboard navigation support', + 'Improve accessibility with ARIA labels', + 'Add page title and meta tags', + 'Review and update copilot instructions', + 'Update project README', + 'Tag first stable release', + 'Plan next sprint backlog', + 'Retrospective on Copilot-assisted dev', + 'Demo application to stakeholders', + 'Collect feedback from demo', + 'Prioritise feedback into backlog', + 'Refine task filtering UX', + 'Add pagination to task list', + 'Explore real database integration', + 'Research authentication options', + 'Evaluate cloud deployment targets', + 'Document lessons learned', + 'Archive completed sprint tasks', + 'Close resolved GitHub issues', +]; +function seedDate(offsetDays) { + const d = new Date('2026-03-01T08:00:00.000Z'); + d.setDate(d.getDate() + offsetDays); + return d.toISOString(); +} +exports.seedTasks = seedTitles.map((title, i) => ({ + id: (0, uuid_1.v4)(), + title, + priority: priorities[i % priorities.length], + status: statuses[i % 4 === 0 ? 1 : 0], // every 4th task is completed + createdAt: seedDate(i), +})); diff --git a/dist/src/services/taskService.js b/dist/src/services/taskService.js new file mode 100644 index 0000000..c5ae19d --- /dev/null +++ b/dist/src/services/taskService.js @@ -0,0 +1,85 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.validPriorities = void 0; +exports.getAllTasks = getAllTasks; +exports.getFilteredTasks = getFilteredTasks; +exports.createTask = createTask; +const uuid_1 = require("uuid"); +const taskSeed_1 = require("./taskSeed"); +/** In-memory task store. Pre-loaded with seed data on startup. */ +const tasks = [...taskSeed_1.seedTasks]; +/** Allowed priority values for a task. */ +exports.validPriorities = ['Low', 'Medium', 'High']; +/** + * Returns all tasks currently held in memory. + * + * @returns {Task[]} Ordered array of tasks in insertion order. + * + * @example + * // GET /tasks + * const all = getAllTasks(); // [] + */ +function getAllTasks() { + return tasks; +} +/** + * Returns tasks filtered by optional status and/or priority. + * Omitting a filter means that dimension is not restricted. + * + * @param {TaskFilters} filters - Optional status and/or priority filter values. + * @returns {Task[]} Array of tasks matching all provided filters. + * + * @example + * // GET /tasks?status=pending + * const pending = getFilteredTasks({ status: 'pending' }); + * + * @example + * // GET /tasks?priority=High + * const highPriority = getFilteredTasks({ priority: 'High' }); + * + * @example + * // GET /tasks?status=pending&priority=High + * const urgent = getFilteredTasks({ status: 'pending', priority: 'High' }); + */ +function getFilteredTasks(filters) { + return tasks.filter((task) => { + if (filters.status !== undefined && task.status !== filters.status) + return false; + if (filters.priority !== undefined && task.priority !== filters.priority) + return false; + return true; + }); +} +/** + * Creates a new task and appends it to the in-memory store. + * + * @param {CreateTaskBody} body - Task creation payload. + * @param {string} body.title - Human-readable task title (must be non-empty). + * @param {Priority} [body.priority='Medium'] - Task urgency level: `'Low'`, `'Medium'`, or `'High'`. + * + * @returns {Task} The newly created task with a generated `id`, `status: 'pending'`, + * and an ISO 8601 `createdAt` timestamp. + * + * @example + * // POST /tasks { "title": "Write tests", "priority": "High" } + * const task = createTask({ title: 'Write tests', priority: 'High' }); + * // { + * // id: '550e8400-...', + * // title: 'Write tests', + * // status: 'pending', + * // priority: 'High', + * // createdAt: '2026-03-31T07:00:00.000Z' + * // } + */ +function createTask(body) { + const { title, priority = 'Medium' } = body; + const newTask = { + id: (0, uuid_1.v4)(), + title: title.trim(), + status: 'pending', + priority, + createdAt: new Date().toISOString(), + }; + tasks.push(newTask); + return newTask; +} diff --git a/server.ts b/server.ts index 9d34ee1..22e0af9 100644 --- a/server.ts +++ b/server.ts @@ -1,15 +1,33 @@ import express, { Request, Response } from 'express'; import { execSync } from 'child_process'; -import { CreateTaskBody } from './src/models/task'; -import { getAllTasks, createTask, validPriorities } from './src/services/taskService'; +import { CreateTaskBody, Priority, Status } from './src/models/task'; +import { getFilteredTasks, createTask, validPriorities } from './src/services/taskService'; + +const validStatuses: Status[] = ['pending', 'completed']; // --- App setup --- const app = express(); app.use(express.json()); -// GET /tasks — return all tasks created during the session -app.get('/tasks', (_req: Request, res: Response) => { - const tasks = getAllTasks(); +// GET /tasks — return tasks, optionally filtered by ?status= and/or ?priority= +app.get('/tasks', (req: Request, res: Response) => { + const { status, priority } = req.query; + + if (status !== undefined && !validStatuses.includes(status as Status)) { + res.status(400).json({ error: `status must be one of: ${validStatuses.join(', ')}.` }); + return; + } + + if (priority !== undefined && !validPriorities.includes(priority as Priority)) { + res.status(400).json({ error: `priority must be one of: ${validPriorities.join(', ')}.` }); + return; + } + + const tasks = getFilteredTasks({ + status: status as Status | undefined, + priority: priority as Priority | undefined, + }); + res.status(200).json({ count: tasks.length, tasks, diff --git a/src/hooks/useTasks.ts b/src/hooks/useTasks.ts index 23e7270..8519c10 100644 --- a/src/hooks/useTasks.ts +++ b/src/hooks/useTasks.ts @@ -1,11 +1,13 @@ import { useEffect, useState } from 'react'; import { Task } from '../models/task'; -import { fetchTasks } from '../services/taskApiService'; +import { fetchTasks, TaskFilterParams } from '../services/taskApiService'; interface UseTasksResult { tasks: Task[]; loading: boolean; error: string | null; + filters: TaskFilterParams; + setFilters: (filters: TaskFilterParams) => void; reload: () => void; } @@ -14,13 +16,14 @@ export function useTasks(): UseTasksResult { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [refreshKey, setRefreshKey] = useState(0); + const [filters, setFilters] = useState({}); useEffect(() => { let cancelled = false; setLoading(true); setError(null); - fetchTasks() + fetchTasks(filters) .then((data) => { if (!cancelled) setTasks(data); }) @@ -34,9 +37,9 @@ export function useTasks(): UseTasksResult { return () => { cancelled = true; }; - }, [refreshKey]); + }, [refreshKey, filters]); const reload = () => setRefreshKey((k) => k + 1); - return { tasks, loading, error, reload }; + return { tasks, loading, error, filters, setFilters, reload }; } diff --git a/src/pages/TasksPage.tsx b/src/pages/TasksPage.tsx index 5a35545..5b539f2 100644 --- a/src/pages/TasksPage.tsx +++ b/src/pages/TasksPage.tsx @@ -1,9 +1,18 @@ import { useTasks } from '../hooks/useTasks'; import { TaskList } from '../components/TaskList'; +import { Priority, Status } from '../models/task'; import '../styles/tasks.css'; export function TasksPage() { - const { tasks, loading, error, reload } = useTasks(); + const { tasks, loading, error, filters, setFilters, reload } = useTasks(); + + function handleStatusChange(e: React.ChangeEvent) { + setFilters({ ...filters, status: e.target.value ? (e.target.value as Status) : undefined }); + } + + function handlePriorityChange(e: React.ChangeEvent) { + setFilters({ ...filters, priority: e.target.value ? (e.target.value as Priority) : undefined }); + } return (
@@ -13,7 +22,31 @@ export function TasksPage() { {loading ? 'Refreshing…' : 'Refresh'} +
+ + +
); } + diff --git a/src/services/taskApiService.ts b/src/services/taskApiService.ts index 360fcfd..92905de 100644 --- a/src/services/taskApiService.ts +++ b/src/services/taskApiService.ts @@ -1,4 +1,4 @@ -import { Task } from '../models/task'; +import { Task, Priority, Status } from '../models/task'; const API_BASE = '/tasks'; @@ -7,8 +7,19 @@ export interface TasksResponse { tasks: Task[]; } -export async function fetchTasks(): Promise { - const response = await fetch(API_BASE); +export interface TaskFilterParams { + status?: Status; + priority?: Priority; +} + +export async function fetchTasks(filters?: TaskFilterParams): Promise { + const params = new URLSearchParams(); + if (filters?.status) params.set('status', filters.status); + if (filters?.priority) params.set('priority', filters.priority); + + const url = params.toString() ? `${API_BASE}?${params.toString()}` : API_BASE; + + const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch tasks: ${response.statusText}`); } diff --git a/src/services/taskService.ts b/src/services/taskService.ts index 2bf439f..a4ebab6 100644 --- a/src/services/taskService.ts +++ b/src/services/taskService.ts @@ -1,5 +1,5 @@ import { v4 as uuidv4 } from 'uuid'; -import { Task, CreateTaskBody, Priority } from '../models/task'; +import { Task, CreateTaskBody, Priority, Status } from '../models/task'; import { seedTasks } from './taskSeed'; /** In-memory task store. Pre-loaded with seed data on startup. */ @@ -8,6 +8,12 @@ const tasks: Task[] = [...seedTasks]; /** Allowed priority values for a task. */ export const validPriorities: Priority[] = ['Low', 'Medium', 'High']; +/** Optional filters for querying tasks. */ +export interface TaskFilters { + status?: Status; + priority?: Priority; +} + /** * Returns all tasks currently held in memory. * @@ -21,6 +27,33 @@ export function getAllTasks(): Task[] { return tasks; } +/** + * Returns tasks filtered by optional status and/or priority. + * Omitting a filter means that dimension is not restricted. + * + * @param {TaskFilters} filters - Optional status and/or priority filter values. + * @returns {Task[]} Array of tasks matching all provided filters. + * + * @example + * // GET /tasks?status=pending + * const pending = getFilteredTasks({ status: 'pending' }); + * + * @example + * // GET /tasks?priority=High + * const highPriority = getFilteredTasks({ priority: 'High' }); + * + * @example + * // GET /tasks?status=pending&priority=High + * const urgent = getFilteredTasks({ status: 'pending', priority: 'High' }); + */ +export function getFilteredTasks(filters: TaskFilters): Task[] { + return tasks.filter((task) => { + if (filters.status !== undefined && task.status !== filters.status) return false; + if (filters.priority !== undefined && task.priority !== filters.priority) return false; + return true; + }); +} + /** * Creates a new task and appends it to the in-memory store. * diff --git a/src/styles/tasks.css b/src/styles/tasks.css index b61eda6..fb283de 100644 --- a/src/styles/tasks.css +++ b/src/styles/tasks.css @@ -10,7 +10,7 @@ display: flex; align-items: center; justify-content: space-between; - margin-bottom: 1.5rem; + margin-bottom: 1rem; } .tasks-page__header h1 { @@ -18,6 +18,29 @@ margin: 0; } +/* ── Filters ────────────────────────────────── */ +.tasks-page__filters { + display: flex; + gap: 0.75rem; + margin-bottom: 1.25rem; + flex-wrap: wrap; +} + +.filter-select { + padding: 0.4rem 0.75rem; + border: 1px solid #d1d5db; + border-radius: 6px; + background: #fff; + font-size: 0.875rem; + cursor: pointer; + color: #374151; +} + +.filter-select:focus { + outline: 2px solid #6366f1; + outline-offset: 1px; +} + /* ── Button ─────────────────────────────────── */ .btn { padding: 0.4rem 1rem; From efbfb16ab72e0daaaaa8ef99cda04be2dd497f47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:09:49 +0000 Subject: [PATCH 3/3] chore: add .gitignore to exclude dist/ and node_modules/ Agent-Logs-Url: https://github.com/CanarysPlayground/TaskManagementApplication/sessions/34e3e125-3aaf-42ab-a04f-b068d1d90c6f Co-authored-by: kavyashri-as <213833080+kavyashri-as@users.noreply.github.com> --- .gitignore | 2 + dist/server.js | 69 ------------------ dist/src/models/task.js | 2 - dist/src/services/taskSeed.js | 120 ------------------------------- dist/src/services/taskService.js | 85 ---------------------- 5 files changed, 2 insertions(+), 276 deletions(-) create mode 100644 .gitignore delete mode 100644 dist/server.js delete mode 100644 dist/src/models/task.js delete mode 100644 dist/src/services/taskSeed.js delete mode 100644 dist/src/services/taskService.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/dist/server.js b/dist/server.js deleted file mode 100644 index 83235dc..0000000 --- a/dist/server.js +++ /dev/null @@ -1,69 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const express_1 = __importDefault(require("express")); -const child_process_1 = require("child_process"); -const taskService_1 = require("./src/services/taskService"); -const validStatuses = ['pending', 'completed']; -// --- App setup --- -const app = (0, express_1.default)(); -app.use(express_1.default.json()); -// GET /tasks — return tasks, optionally filtered by ?status= and/or ?priority= -app.get('/tasks', (req, res) => { - const { status, priority } = req.query; - if (status !== undefined && !validStatuses.includes(status)) { - res.status(400).json({ error: `status must be one of: ${validStatuses.join(', ')}.` }); - return; - } - if (priority !== undefined && !taskService_1.validPriorities.includes(priority)) { - res.status(400).json({ error: `priority must be one of: ${taskService_1.validPriorities.join(', ')}.` }); - return; - } - const tasks = (0, taskService_1.getFilteredTasks)({ - status: status, - priority: priority, - }); - res.status(200).json({ - count: tasks.length, - tasks, - }); -}); -// POST /tasks — create a new task dynamically -app.post('/tasks', (req, res) => { - const { title, priority = 'Medium' } = req.body; - if (!title || typeof title !== 'string' || title.trim() === '') { - res.status(400).json({ error: 'title is required and must be a non-empty string.' }); - return; - } - if (!taskService_1.validPriorities.includes(priority)) { - res.status(400).json({ error: `priority must be one of: ${taskService_1.validPriorities.join(', ')}.` }); - return; - } - const newTask = (0, taskService_1.createTask)({ title, priority }); - res.status(201).json(newTask); -}); -// --- Start server --- -const PORT = process.env.PORT ?? 3001; -function startServer() { - const server = app.listen(PORT, () => { - console.log(`Task API running on http://localhost:${PORT}`); - console.log(' GET /tasks – list all tasks'); - console.log(' POST /tasks – create a task { title, priority? }'); - }); - server.on('error', (err) => { - if (err.code === 'EADDRINUSE') { - console.log(`Port ${PORT} is busy. Killing existing process and retrying...`); - try { - (0, child_process_1.execSync)(`powershell -Command "Get-Process -Id (Get-NetTCPConnection -LocalPort ${PORT} -ErrorAction SilentlyContinue).OwningProcess -ErrorAction SilentlyContinue | Stop-Process -Force"`, { stdio: 'ignore' }); - setTimeout(startServer, 500); - } - catch { - console.error(`Could not free port ${PORT}. Kill it manually and try again.`); - process.exit(1); - } - } - }); -} -startServer(); diff --git a/dist/src/models/task.js b/dist/src/models/task.js deleted file mode 100644 index c8ad2e5..0000000 --- a/dist/src/models/task.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/services/taskSeed.js b/dist/src/services/taskSeed.js deleted file mode 100644 index 31fbfbc..0000000 --- a/dist/src/services/taskSeed.js +++ /dev/null @@ -1,120 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.seedTasks = void 0; -const uuid_1 = require("uuid"); -const priorities = ['Low', 'Medium', 'High']; -const statuses = ['pending', 'completed']; -const seedTitles = [ - 'Set up project repository', - 'Define initial requirements', - 'Design database schema', - 'Configure CI/CD pipeline', - 'Write API specification', - 'Implement user authentication', - 'Build task creation endpoint', - 'Build task listing endpoint', - 'Build task update endpoint', - 'Build task delete endpoint', - 'Add input validation to API', - 'Write unit tests for taskService', - 'Write integration tests for routes', - 'Set up ESLint and Prettier', - 'Configure TypeScript strict mode', - 'Create React project scaffold', - 'Build TaskItem component', - 'Build TaskList component', - 'Build TaskForm component', - 'Wire TaskForm to POST /tasks', - 'Wire TaskList to GET /tasks', - 'Add loading state to TaskList', - 'Add error state to TaskList', - 'Add empty state to TaskList', - 'Extract useTasks custom hook', - 'Extract taskApiService module', - 'Add priority badge styles', - 'Add status indicator styles', - 'Make UI responsive for mobile', - 'Add Refresh button to TasksPage', - 'Implement task filtering by status', - 'Implement task filtering by priority', - 'Add sort by creation date', - 'Add sort by priority', - 'Persist tasks to local storage', - 'Load tasks from local storage on startup', - 'Clear completed tasks feature', - 'Mark all tasks as complete feature', - 'Add task count summary header', - 'Write JSDoc for taskService', - 'Write JSDoc for taskApiService', - 'Write JSDoc for useTasks hook', - 'Document API endpoints in README', - 'Fix CORS configuration on API', - 'Add rate limiting middleware', - 'Set up environment variables', - 'Add health check endpoint', - 'Review and refactor TaskItem', - 'Review and refactor TaskList', - 'Review and refactor TasksPage', - 'Validate TypeScript across client', - 'Validate TypeScript across server', - 'Fix moduleResolution for Vite config', - 'Split tsconfig for client and server', - 'Add Vite proxy config for API', - 'Create custom agent for Task App', - 'Create ui-review-skill', - 'Create task-ui prompt file', - 'Review separation of concerns in components', - 'Extract TaskListLoading component', - 'Extract TaskListError component', - 'Extract TaskListEmpty component', - 'Rename tick to refreshKey in useTasks', - 'Add CSS import to sub-components', - 'Add btn class to Retry button', - 'Extract isCompleted const in TaskItem', - 'Add seed data for demo purposes', - 'Verify 100 tasks load on startup', - 'Test POST /tasks with new seed data', - 'Test GET /tasks returns all seeded tasks', - 'Review priority distribution in seed data', - 'Review status distribution in seed data', - 'Add createdAt timestamps to seed tasks', - 'Confirm seed data matches Task model', - 'Review task titles for clarity', - 'Write end-to-end test for task flow', - 'Add task detail view page', - 'Add task edit modal', - 'Add delete confirmation dialog', - 'Improve error messages in API', - 'Improve empty-state copy in UI', - 'Add keyboard navigation support', - 'Improve accessibility with ARIA labels', - 'Add page title and meta tags', - 'Review and update copilot instructions', - 'Update project README', - 'Tag first stable release', - 'Plan next sprint backlog', - 'Retrospective on Copilot-assisted dev', - 'Demo application to stakeholders', - 'Collect feedback from demo', - 'Prioritise feedback into backlog', - 'Refine task filtering UX', - 'Add pagination to task list', - 'Explore real database integration', - 'Research authentication options', - 'Evaluate cloud deployment targets', - 'Document lessons learned', - 'Archive completed sprint tasks', - 'Close resolved GitHub issues', -]; -function seedDate(offsetDays) { - const d = new Date('2026-03-01T08:00:00.000Z'); - d.setDate(d.getDate() + offsetDays); - return d.toISOString(); -} -exports.seedTasks = seedTitles.map((title, i) => ({ - id: (0, uuid_1.v4)(), - title, - priority: priorities[i % priorities.length], - status: statuses[i % 4 === 0 ? 1 : 0], // every 4th task is completed - createdAt: seedDate(i), -})); diff --git a/dist/src/services/taskService.js b/dist/src/services/taskService.js deleted file mode 100644 index c5ae19d..0000000 --- a/dist/src/services/taskService.js +++ /dev/null @@ -1,85 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.validPriorities = void 0; -exports.getAllTasks = getAllTasks; -exports.getFilteredTasks = getFilteredTasks; -exports.createTask = createTask; -const uuid_1 = require("uuid"); -const taskSeed_1 = require("./taskSeed"); -/** In-memory task store. Pre-loaded with seed data on startup. */ -const tasks = [...taskSeed_1.seedTasks]; -/** Allowed priority values for a task. */ -exports.validPriorities = ['Low', 'Medium', 'High']; -/** - * Returns all tasks currently held in memory. - * - * @returns {Task[]} Ordered array of tasks in insertion order. - * - * @example - * // GET /tasks - * const all = getAllTasks(); // [] - */ -function getAllTasks() { - return tasks; -} -/** - * Returns tasks filtered by optional status and/or priority. - * Omitting a filter means that dimension is not restricted. - * - * @param {TaskFilters} filters - Optional status and/or priority filter values. - * @returns {Task[]} Array of tasks matching all provided filters. - * - * @example - * // GET /tasks?status=pending - * const pending = getFilteredTasks({ status: 'pending' }); - * - * @example - * // GET /tasks?priority=High - * const highPriority = getFilteredTasks({ priority: 'High' }); - * - * @example - * // GET /tasks?status=pending&priority=High - * const urgent = getFilteredTasks({ status: 'pending', priority: 'High' }); - */ -function getFilteredTasks(filters) { - return tasks.filter((task) => { - if (filters.status !== undefined && task.status !== filters.status) - return false; - if (filters.priority !== undefined && task.priority !== filters.priority) - return false; - return true; - }); -} -/** - * Creates a new task and appends it to the in-memory store. - * - * @param {CreateTaskBody} body - Task creation payload. - * @param {string} body.title - Human-readable task title (must be non-empty). - * @param {Priority} [body.priority='Medium'] - Task urgency level: `'Low'`, `'Medium'`, or `'High'`. - * - * @returns {Task} The newly created task with a generated `id`, `status: 'pending'`, - * and an ISO 8601 `createdAt` timestamp. - * - * @example - * // POST /tasks { "title": "Write tests", "priority": "High" } - * const task = createTask({ title: 'Write tests', priority: 'High' }); - * // { - * // id: '550e8400-...', - * // title: 'Write tests', - * // status: 'pending', - * // priority: 'High', - * // createdAt: '2026-03-31T07:00:00.000Z' - * // } - */ -function createTask(body) { - const { title, priority = 'Medium' } = body; - const newTask = { - id: (0, uuid_1.v4)(), - title: title.trim(), - status: 'pending', - priority, - createdAt: new Date().toISOString(), - }; - tasks.push(newTask); - return newTask; -}