feat(frontend): Create Sprint 4 Stories and Tasks for Story Management
Created comprehensive Story and Task files for Sprint 4 frontend implementation: Story 1: Story Detail Page Foundation (P0 Critical - 3 days) - 6 tasks: route creation, header, sidebar, data loading, Edit/Delete, responsive design - Fixes critical 404 error when clicking Story cards - Two-column layout consistent with Epic detail page Story 2: Task Management in Story Detail (P0 Critical - 2 days) - 6 tasks: API verification, hooks, TaskList, TaskCard, TaskForm, integration - Complete Task CRUD with checkbox status toggle - Filters, sorting, and optimistic UI updates Story 3: Enhanced Story Form (P1 High - 2 days) - 6 tasks: acceptance criteria, assignee selector, tags, story points, integration - Aligns with UX design specification - Backward compatible with existing Stories Story 4: Quick Add Story Workflow (P1 High - 2 days) - 5 tasks: inline form, keyboard shortcuts, batch creation, navigation - Rapid Story creation with minimal fields - Keyboard shortcut (Cmd/Ctrl + N) Story 5: Story Card Component (P2 Medium - 1 day) - 4 tasks: component variants, visual states, Task count, optimization - Reusable component with list/kanban/compact variants - React.memo optimization Story 6: Kanban Story Creation Enhancement (P2 Optional - 2 days) - 4 tasks: Epic card enhancement, inline form, animation, real-time updates - Contextual Story creation from Kanban - Stretch goal - implement only if ahead of schedule Total: 6 Stories, 31 Tasks, 12 days estimated Priority breakdown: P0 (2), P1 (2), P2 (2 optional) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
309
docs/plans/sprint_4_story_1_task_5.md
Normal file
309
docs/plans/sprint_4_story_1_task_5.md
Normal file
@@ -0,0 +1,309 @@
|
||||
---
|
||||
task_id: sprint_4_story_1_task_5
|
||||
story_id: sprint_4_story_1
|
||||
sprint_id: sprint_4
|
||||
status: not_started
|
||||
type: frontend
|
||||
assignee: Frontend Developer 1
|
||||
created_date: 2025-11-05
|
||||
estimated_hours: 3
|
||||
---
|
||||
|
||||
# Task 5: Add Edit and Delete Actions with Dialogs
|
||||
|
||||
## Description
|
||||
|
||||
Implement Edit and Delete functionality for Stories in the detail page. Reuse existing Story form dialog for editing and create a confirmation dialog for deletion with cascade warning.
|
||||
|
||||
## What to Do
|
||||
|
||||
1. Add state management for dialog visibility (Edit dialog, Delete dialog)
|
||||
2. Integrate existing Story form component for Edit action
|
||||
3. Create Delete confirmation dialog
|
||||
4. Show cascade warning if Story has Tasks (list affected Tasks)
|
||||
5. Implement delete mutation with error handling
|
||||
6. Add success/error toast notifications
|
||||
7. Handle post-delete navigation (redirect to Epic detail page)
|
||||
8. Test Edit and Delete flows
|
||||
|
||||
## Files to Modify
|
||||
|
||||
- `app/(dashboard)/stories/[id]/page.tsx` (modify, ~80 lines added for dialogs)
|
||||
|
||||
## Implementation Details
|
||||
|
||||
```typescript
|
||||
// app/(dashboard)/stories/[id]/page.tsx
|
||||
'use client';
|
||||
|
||||
import { use, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useStory, useUpdateStory, useDeleteStory } from '@/lib/hooks/use-stories';
|
||||
import { useTasks } from '@/lib/hooks/use-tasks';
|
||||
import { StoryForm } from '@/components/projects/story-form';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { toast } from 'sonner';
|
||||
import { Loader2, AlertTriangle } from 'lucide-react';
|
||||
|
||||
export default function StoryDetailPage({ params }: StoryDetailPageProps) {
|
||||
const { id } = use(params);
|
||||
const router = useRouter();
|
||||
|
||||
// Fetch data
|
||||
const { data: story } = useStory(id);
|
||||
const { data: tasks = [] } = useTasks(id);
|
||||
|
||||
// Dialog state
|
||||
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||
|
||||
// Mutations
|
||||
const updateStory = useUpdateStory();
|
||||
const deleteStory = useDeleteStory();
|
||||
|
||||
// Edit Story handler
|
||||
const handleEditStory = async (data: UpdateStoryDto) => {
|
||||
try {
|
||||
await updateStory.mutateAsync({ id: story.id, data });
|
||||
setIsEditDialogOpen(false);
|
||||
toast.success('Story updated successfully');
|
||||
} catch (error) {
|
||||
toast.error('Failed to update Story');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Delete Story handler
|
||||
const handleDeleteStory = async () => {
|
||||
try {
|
||||
await deleteStory.mutateAsync(story.id);
|
||||
toast.success('Story deleted successfully');
|
||||
// Redirect to parent Epic detail page
|
||||
router.push(`/epics/${story.epicId}`);
|
||||
} catch (error) {
|
||||
toast.error('Failed to delete Story');
|
||||
console.error(error);
|
||||
setIsDeleteDialogOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-6 max-w-7xl">
|
||||
{/* ... existing content ... */}
|
||||
|
||||
<StoryHeader
|
||||
story={story}
|
||||
onEdit={() => setIsEditDialogOpen(true)}
|
||||
onDelete={() => setIsDeleteDialogOpen(true)}
|
||||
/>
|
||||
|
||||
{/* ... rest of content ... */}
|
||||
|
||||
{/* Edit Story Dialog */}
|
||||
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
|
||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Story</DialogTitle>
|
||||
<DialogDescription>
|
||||
Update Story details. Changes will be saved immediately.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<StoryForm
|
||||
mode="edit"
|
||||
initialData={{
|
||||
epicId: story.epicId,
|
||||
projectId: story.projectId,
|
||||
title: story.title,
|
||||
description: story.description,
|
||||
priority: story.priority,
|
||||
estimatedHours: story.estimatedHours,
|
||||
}}
|
||||
onSubmit={handleEditStory}
|
||||
onCancel={() => setIsEditDialogOpen(false)}
|
||||
isSubmitting={updateStory.isPending}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Story Confirmation Dialog */}
|
||||
<AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-destructive" />
|
||||
Delete Story?
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will permanently delete <strong>{story?.title}</strong> and all associated data.
|
||||
This action cannot be undone.
|
||||
|
||||
{/* Cascade warning if Tasks exist */}
|
||||
{tasks.length > 0 && (
|
||||
<div className="mt-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md">
|
||||
<p className="font-medium text-destructive mb-2">
|
||||
⚠️ This Story has {tasks.length} Task{tasks.length > 1 ? 's' : ''} that will also be deleted:
|
||||
</p>
|
||||
<ul className="list-disc list-inside text-sm space-y-1">
|
||||
{tasks.slice(0, 5).map((task) => (
|
||||
<li key={task.id}>{task.title}</li>
|
||||
))}
|
||||
{tasks.length > 5 && (
|
||||
<li className="italic">... and {tasks.length - 5} more</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={deleteStory.isPending}>
|
||||
Cancel
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleDeleteStory}
|
||||
disabled={deleteStory.isPending}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{deleteStory.isPending && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
||||
Delete Story
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Edit button opens Story form dialog with current Story data
|
||||
- [ ] Story form displays in "edit" mode with pre-filled values
|
||||
- [ ] Editing Story updates data correctly
|
||||
- [ ] Edit success shows toast notification
|
||||
- [ ] Edit error shows error toast
|
||||
- [ ] Dialog closes after successful edit
|
||||
- [ ] Delete button opens confirmation dialog
|
||||
- [ ] Confirmation dialog shows Story title
|
||||
- [ ] If Story has Tasks, shows cascade warning with Task list
|
||||
- [ ] Delete action removes Story and navigates to Epic detail page
|
||||
- [ ] Delete success shows toast notification
|
||||
- [ ] Delete error shows error toast and keeps user on page
|
||||
- [ ] Dialog buttons disabled during mutation (loading state)
|
||||
- [ ] ESC key closes dialogs
|
||||
- [ ] Outside click closes dialogs (configurable)
|
||||
|
||||
## Testing
|
||||
|
||||
**Manual Testing**:
|
||||
|
||||
**Edit Story Flow**:
|
||||
1. Click Edit button → Verify dialog opens
|
||||
2. Verify form pre-filled with current Story data
|
||||
3. Change title → Save → Verify title updates on page
|
||||
4. Change description → Save → Verify description updates
|
||||
5. Change priority → Save → Verify priority badge updates
|
||||
6. Test validation → Submit empty title → Verify error message
|
||||
7. Click Cancel → Verify dialog closes without saving
|
||||
8. Press ESC → Verify dialog closes
|
||||
|
||||
**Delete Story Flow**:
|
||||
1. Click Delete button → Verify confirmation dialog opens
|
||||
2. Verify Story title displayed in confirmation message
|
||||
3. If Story has Tasks → Verify cascade warning shows with Task list
|
||||
4. Click Cancel → Verify dialog closes, Story not deleted
|
||||
5. Click Delete Story → Verify Story deleted
|
||||
6. Verify redirected to Epic detail page
|
||||
7. Verify success toast shown
|
||||
8. Go to Epic page → Verify Story no longer in list
|
||||
|
||||
**Error Handling**:
|
||||
1. Disconnect internet
|
||||
2. Try to edit Story → Verify error toast shown
|
||||
3. Try to delete Story → Verify error toast shown
|
||||
4. Verify user stays on page after error
|
||||
|
||||
**E2E Test**:
|
||||
```typescript
|
||||
test('user can edit story', async ({ page }) => {
|
||||
await page.goto('/stories/story-123');
|
||||
|
||||
// Open edit dialog
|
||||
await page.click('[aria-label="Edit Story"]');
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
|
||||
// Edit title
|
||||
const titleInput = page.locator('[name="title"]');
|
||||
await titleInput.fill('Updated Story Title');
|
||||
|
||||
// Save
|
||||
await page.click('button:has-text("Save")');
|
||||
|
||||
// Verify updated
|
||||
await expect(page.locator('h1')).toContainText('Updated Story Title');
|
||||
await expect(page.getByText('Story updated successfully')).toBeVisible();
|
||||
});
|
||||
|
||||
test('user can delete story', async ({ page }) => {
|
||||
await page.goto('/stories/story-123');
|
||||
|
||||
// Open delete dialog
|
||||
await page.click('[aria-label="Delete Story"]');
|
||||
await expect(page.getByRole('alertdialog')).toBeVisible();
|
||||
|
||||
// Confirm delete
|
||||
await page.click('button:has-text("Delete Story")');
|
||||
|
||||
// Verify redirected
|
||||
await expect(page).toHaveURL(/\/epics\//);
|
||||
await expect(page.getByText('Story deleted successfully')).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
**Prerequisites**:
|
||||
- Task 1, 2, 3, 4 (page, header, sidebar, data loading must exist)
|
||||
- ✅ StoryForm component (already exists)
|
||||
- ✅ `useUpdateStory()` hook (already exists)
|
||||
- ✅ `useDeleteStory()` hook (already exists)
|
||||
- ✅ `useTasks(storyId)` hook (for cascade warning)
|
||||
- ✅ shadcn/ui Dialog, AlertDialog components
|
||||
- ✅ sonner for toast notifications
|
||||
|
||||
**Blocks**:
|
||||
- None (final task for Story 1)
|
||||
|
||||
## Estimated Time
|
||||
|
||||
3 hours
|
||||
|
||||
## Notes
|
||||
|
||||
**Cascade Warning**: Show list of Tasks that will be deleted when Story is deleted. This helps users understand the impact of deletion and prevents accidental data loss.
|
||||
|
||||
**Navigation After Delete**: Always redirect to the parent Epic detail page after successful deletion. This prevents users from staying on a deleted Story page.
|
||||
|
||||
**Form Reuse**: The StoryForm component already exists and handles both "create" and "edit" modes. No changes needed to the form itself.
|
||||
|
||||
**Error Handling**: Use optimistic UI for updates - update immediately, revert on error. For deletes, wait for confirmation before navigating.
|
||||
Reference in New Issue
Block a user