fix(frontend): restore member self-assignment for shifts and tasks
All checks were successful
CI Pipeline / Backend Build & Test (push) Successful in 1m12s
CI Pipeline / Frontend Lint, Test & Build (push) Successful in 35s
CI Pipeline / Infrastructure Validation (push) Successful in 4s
CI Pipeline / Backend Build & Test (pull_request) Successful in 52s
CI Pipeline / Frontend Lint, Test & Build (pull_request) Successful in 33s
CI Pipeline / Infrastructure Validation (pull_request) Successful in 4s
All checks were successful
CI Pipeline / Backend Build & Test (push) Successful in 1m12s
CI Pipeline / Frontend Lint, Test & Build (push) Successful in 35s
CI Pipeline / Infrastructure Validation (push) Successful in 4s
CI Pipeline / Backend Build & Test (pull_request) Successful in 52s
CI Pipeline / Frontend Lint, Test & Build (pull_request) Successful in 33s
CI Pipeline / Infrastructure Validation (pull_request) Successful in 4s
Root Cause: - Shift: Next.js 16.1.6 incompatible rewrite pattern caused runtime SyntaxError - Task: Missing self-assignment UI for member role Fix: - Updated next.config.ts rewrite pattern from regex to wildcard syntax - Added "Assign to Me" button to task detail page with useSession integration - Added test coverage for self-assignment behavior with session mocks Testing: - Lint: ✅ PASS (ESLint v9) - Tests: ✅ 47/47 PASS (Vitest v4.0.18) - Build: ✅ PASS (Next.js 16.1.6, 12 routes) Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -6,6 +6,7 @@ import { useTask, useUpdateTask } from '@/hooks/useTasks';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
const VALID_TRANSITIONS: Record<string, string[]> = {
|
||||
Open: ['Assigned'],
|
||||
@@ -26,6 +27,7 @@ export default function TaskDetailPage({ params }: { params: Promise<{ id: strin
|
||||
const resolvedParams = use(params);
|
||||
const { data: task, isLoading, error } = useTask(resolvedParams.id);
|
||||
const { mutate: updateTask, isPending } = useUpdateTask();
|
||||
const { data: session } = useSession();
|
||||
|
||||
if (isLoading) return <div className="p-8">Loading task...</div>;
|
||||
if (error || !task) return <div className="p-8 text-red-500">Failed to load task.</div>;
|
||||
@@ -36,6 +38,12 @@ export default function TaskDetailPage({ params }: { params: Promise<{ id: strin
|
||||
updateTask({ id: task.id, data: { status: newStatus } });
|
||||
};
|
||||
|
||||
const handleAssignToMe = () => {
|
||||
if (session?.user?.id) {
|
||||
updateTask({ id: task.id, data: { assigneeId: session.user.id } });
|
||||
}
|
||||
};
|
||||
|
||||
const getTransitionLabel = (status: string, newStatus: string) => {
|
||||
if (status === 'Review' && newStatus === 'InProgress') return 'Back to InProgress';
|
||||
if (newStatus === 'Done') return 'Mark as Done';
|
||||
@@ -93,6 +101,15 @@ export default function TaskDetailPage({ params }: { params: Promise<{ id: strin
|
||||
<div className="pt-6 border-t">
|
||||
<h3 className="text-lg font-medium mb-4">Actions</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{!task.assigneeId && session?.user && (
|
||||
<Button
|
||||
onClick={handleAssignToMe}
|
||||
disabled={isPending}
|
||||
variant="outline"
|
||||
>
|
||||
{isPending ? 'Assigning...' : 'Assign to Me'}
|
||||
</Button>
|
||||
)}
|
||||
{validTransitions.map((nextStatus) => (
|
||||
<Button
|
||||
key={nextStatus}
|
||||
|
||||
@@ -11,6 +11,13 @@ vi.mock('next/navigation', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('next-auth/react', () => ({
|
||||
useSession: vi.fn(() => ({
|
||||
data: { user: { id: 'user-123' } },
|
||||
status: 'authenticated',
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/useTasks', () => ({
|
||||
useTask: vi.fn(),
|
||||
useUpdateTask: vi.fn(),
|
||||
@@ -74,4 +81,63 @@ describe('TaskDetailPage', () => {
|
||||
expect(screen.getByText('Mark as Done')).toBeInTheDocument();
|
||||
expect(screen.getByText('Back to InProgress')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Assign to Me button when task unassigned and session exists', async () => {
|
||||
(useTask as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
data: {
|
||||
id: '1',
|
||||
title: 'Task 1',
|
||||
status: 'Open',
|
||||
assigneeId: null,
|
||||
description: 'Desc',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01'
|
||||
},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const params = Promise.resolve({ id: '1' });
|
||||
await act(async () => {
|
||||
render(<TaskDetailPage params={params} />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('Assign to Me')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls updateTask with assigneeId when Assign to Me clicked', async () => {
|
||||
const mockMutate = vi.fn();
|
||||
(useTask as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
data: {
|
||||
id: '1',
|
||||
title: 'Task 1',
|
||||
status: 'Open',
|
||||
assigneeId: null,
|
||||
description: 'Desc',
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-01-01'
|
||||
},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
(useUpdateTask as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
mutate: mockMutate,
|
||||
isPending: false,
|
||||
});
|
||||
|
||||
const params = Promise.resolve({ id: '1' });
|
||||
await act(async () => {
|
||||
render(<TaskDetailPage params={params} />);
|
||||
});
|
||||
|
||||
const button = screen.getByText('Assign to Me');
|
||||
await act(async () => {
|
||||
button.click();
|
||||
});
|
||||
|
||||
expect(mockMutate).toHaveBeenCalledWith({
|
||||
id: '1',
|
||||
data: { assigneeId: 'user-123' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user