feat(tasks): project-centric task management redesign

- Task board uses per-project workflow state columns with status
  fallback when no project is selected; adds calendar view tab
- Chip-based filter bar (search, project, state, priority, due date)
  with quick-add bar and slide-over task detail panel
- Project detail switches to left-nav layout with progress summary
  and per-section counts; projects list gains search, status
  filter, and richer cards
- Sidebar gains expandable active-projects sub-nav; calendar
  unified view gains project filter
- API: task due_after/due_before filters, GET /tasks/grouped,
  GET /projects/:id/stats, calendar unified project_id filter
- Apply Buzzbee design tokens; remove accidentally committed
  apps/web/node_modules self-symlink
This commit is contained in:
2026-09-10 10:49:41 +00:00
parent f3b1fd709a
commit ddcb707190
15 changed files with 1394 additions and 317 deletions
+10 -2
View File
@@ -34,7 +34,7 @@ test.describe('Tasks', () => {
});
});
test('opens the task detail page when a task card is clicked', async ({ page }) => {
test('opens the task detail panel when a task card is clicked', async ({ page }) => {
// Make sure a task exists before trying to open it.
if (!(await page.getByText(testTasks.title, { exact: true }).isVisible().catch(() => false))) {
await page.getByRole('button', { name: /new task/i }).click();
@@ -46,6 +46,11 @@ test.describe('Tasks', () => {
await page.getByText(testTasks.title, { exact: true }).first().click();
// Clicking a card opens the slide-over detail panel.
await expect(page.getByText('Task details')).toBeVisible({ timeout: 10_000 });
// The panel offers a link to the full detail page.
await page.getByRole('button', { name: /open full page/i }).click();
await page.waitForURL('**/tasks/*', { timeout: 10_000 });
// The detail page renders the task title as a heading.
@@ -54,10 +59,13 @@ test.describe('Tasks', () => {
).toBeVisible({ timeout: 10_000 });
});
test('switches between board and list views', async ({ page }) => {
test('switches between board, list, and calendar views', async ({ page }) => {
await page.getByRole('tab', { name: /list view/i }).click();
await expect(page.getByRole('columnheader', { name: 'Title' })).toBeVisible();
await page.getByRole('tab', { name: /calendar view/i }).click();
await expect(page.getByText(/today/i).first()).toBeVisible();
await page.getByRole('tab', { name: /board view/i }).click();
await expect(page.getByRole('heading', { name: 'Todo' })).toBeVisible();
});