Skip to content

Commit 317f64f

Browse files
committed
stash
1 parent 2250ef4 commit 317f64f

30 files changed

Lines changed: 2902 additions & 297 deletions

.github/instructions/development.md.instructions.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ make all components responsive by default. Use CSS media queries or utility clas
1212
Use CSS Grid or Flexbox for layout. Avoid using fixed widths or heights unless absolutely necessary.
1313
All components should be composable with all props and actions being passed down to child components. This allows for greater flexibility and reusability. Do not hardcode test data within the components. where sensible, put test data within a "tests" folder in the root of the project.
1414

15+
Testing is vitest and should be done using the Vitest framework. Write unit tests for all components, focusing on functionality, rendering, and interactions. Use the `@testing-library/react` library for testing React components.
16+
Use the `@testing-library/jest-dom` library for custom matchers like `toBeInTheDocument`, `toHaveClass`, and `toHaveStyle`. This helps make tests more readable and expressive.
17+
Use the `@testing-library/user-event` library for simulating user interactions in tests. This allows for more realistic testing of component behavior.
18+
Use the `@testing-library/react-hooks` library for testing custom hooks. This allows for isolated testing of hook logic without the need for a full component render.
19+
Use the `@testing-library/react` library's `render` function to render components in tests. This provides a clean and isolated environment for testing component behavior.
20+
Use the `@testing-library/react` library's `screen` object to query elements in tests. This provides a consistent and readable way to access elements in the rendered component.
21+
Use the `@testing-library/react` library's `fireEvent` function for simulating events in tests. This allows for testing of user interactions and component behavior in response to those interactions.
22+
Use the `@testing-library/react` library's `waitFor` function for asynchronous tests. This allows for waiting for elements to appear or change state before making assertions.
23+
Use the `@testing-library/react` library's `act` function for wrapping state updates in tests. This ensures that all updates are processed before making assertions.
24+
Use the `@testing-library/react` library's `cleanup` function to unmount components after each test. This helps prevent memory leaks and ensures a clean testing environment.
25+
Use the `@testing-library/react` library's `debug` function to log the current state of the component in tests.
26+
This can be useful for debugging test failures and understanding the component's state at the time of the failure.
27+
28+
Don't try and guess command line tests to run. pnpm run build pnpm run typecheck and pnpm run test will run the tests we need.
29+
Dont run storybook as i have it running in a docker container. If you need to test components, use the Storybook instance running in the container.
1530

1631
Use Framer Motion for animations and transitions. Ensure that animations are smooth and enhance the user experience without being distracting.
1732
Use Shadcn UI components where possible for consistency in design and behavior. If a component does not exist in Shadcn UI, create a custom component that adheres to the design system.

src/components/DigitalColleagues/ManagementSidebar.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ interface ManagementSidebarProps {
1818
projects: Project[];
1919
epics: Epic[];
2020
sprints: Sprint[];
21-
currentView: 'kanban' | 'planning' | 'files' | 'epics';
21+
currentView: 'kanban' | 'planning' | 'tasks' | 'files' | 'epics';
2222
onUpdateProject: (projectId: string, updates: Partial<Project>) => void;
2323
onDeleteProject: (projectId: string) => void;
2424
onAddProject: (project: Omit<Project, 'id'>) => void;
@@ -28,7 +28,7 @@ interface ManagementSidebarProps {
2828
onAddSprint: (sprint: Omit<Sprint, 'id'>) => void;
2929
onUpdateSprint: (sprintId: string, updates: Partial<Sprint>) => void;
3030
onDeleteSprint: (sprintId: string) => void;
31-
onViewChange: (view: 'kanban' | 'planning' | 'files' | 'epics') => void;
31+
onViewChange: (view: 'kanban' | 'planning' | 'tasks' | 'files' | 'epics') => void;
3232
mobileMenuOpen: boolean;
3333
onToggleMobileMenu: () => void;
3434
}
@@ -368,6 +368,22 @@ const ManagementSidebarContent: React.FC<ManagementSidebarProps> = ({
368368
Files
369369
</span>
370370
</button>
371+
372+
<button
373+
onClick={() => {
374+
onViewChange('tasks');
375+
setActiveSection(null);
376+
}}
377+
className={`flex-1 w-12 flex items-center justify-center text-white transition-all duration-200 relative overflow-hidden group/btn ${
378+
currentView === 'tasks' ? 'bg-purple-600' : 'bg-purple-500 hover:bg-purple-600'
379+
} hover:w-28 hover:justify-start hover:pl-3`}
380+
title="Tasks"
381+
>
382+
<Calendar className="h-4 w-4 flex-shrink-0" />
383+
<span className="text-xs font-medium whitespace-nowrap opacity-0 group-hover/btn:opacity-100 transition-opacity duration-200 absolute left-8">
384+
Tasks
385+
</span>
386+
</button>
371387
</div>
372388

373389
{/* Mobile Overlay - Truly full screen */}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import { action } from '@storybook/addon-actions';
3+
import { TasksView } from './TasksView';
4+
import type { Reminder, DigitalColleague } from './TasksView';
5+
6+
const meta: Meta<typeof TasksView> = {
7+
title: 'DC/Views/TasksView',
8+
component: TasksView,
9+
parameters: {
10+
layout: 'fullscreen',
11+
},
12+
tags: ['autodocs'],
13+
};
14+
15+
export default meta;
16+
type Story = StoryObj<typeof TasksView>;
17+
18+
const mockColleagues: DigitalColleague[] = [
19+
{ id: '1', name: 'Alex AI', department: 'Development', role: 'Senior Developer' },
20+
{ id: '2', name: 'Maya Bot', department: 'Design', role: 'UX Designer' },
21+
{ id: '3', name: 'Sam Assistant', department: 'Marketing', role: 'Marketing Specialist' },
22+
{ id: '4', name: 'Jordan Helper', department: 'Support', role: 'Customer Success' },
23+
{ id: '5', name: 'Riley Automation', department: 'Operations', role: 'DevOps Engineer' },
24+
];
25+
26+
const mockReminders: Reminder[] = [
27+
{
28+
id: '1',
29+
title: 'Review quarterly report',
30+
description: 'Go through Q4 performance metrics and prepare summary for the team meeting',
31+
dueDate: new Date(2024, 2, 15), // March 15, 2024
32+
dueTime: '14:30',
33+
colleague: mockColleagues[0],
34+
isCompleted: false,
35+
isRecurring: false,
36+
priority: 'high',
37+
reminderEnabled: true,
38+
reminderMinutes: 30,
39+
createdAt: new Date(2024, 2, 10),
40+
tags: ['quarterly', 'review', 'urgent']
41+
},
42+
{
43+
id: '2',
44+
title: 'Weekly team standup',
45+
description: 'Join the weekly team synchronization meeting to discuss progress and blockers',
46+
dueDate: new Date(2024, 2, 18), // March 18, 2024
47+
dueTime: '10:00',
48+
colleague: mockColleagues[1],
49+
isCompleted: false,
50+
isRecurring: true,
51+
recurrencePattern: 'weekly',
52+
recurrenceInterval: 1,
53+
priority: 'medium',
54+
reminderEnabled: true,
55+
reminderMinutes: 15,
56+
createdAt: new Date(2024, 2, 11),
57+
tags: ['meeting', 'standup', 'weekly']
58+
},
59+
{
60+
id: '3',
61+
title: 'Update project documentation',
62+
description: 'Refresh the README and add new API documentation for the latest features',
63+
dueDate: new Date(2024, 2, 20), // March 20, 2024
64+
colleague: mockColleagues[2],
65+
isCompleted: true,
66+
isRecurring: false,
67+
priority: 'low',
68+
reminderEnabled: false,
69+
createdAt: new Date(2024, 2, 12),
70+
completedAt: new Date(2024, 2, 19),
71+
tags: ['documentation', 'api']
72+
},
73+
{
74+
id: '4',
75+
title: 'Follow up on customer feedback',
76+
description: 'Reach out to customers who provided feedback last week and discuss next steps',
77+
dueDate: new Date(2024, 2, 14), // March 14, 2024 (overdue)
78+
dueTime: '16:00',
79+
colleague: mockColleagues[3],
80+
isCompleted: false,
81+
isRecurring: false,
82+
priority: 'high',
83+
reminderEnabled: true,
84+
reminderMinutes: 60,
85+
createdAt: new Date(2024, 2, 8),
86+
tags: ['customer', 'feedback', 'follow-up']
87+
},
88+
{
89+
id: '5',
90+
title: 'Monthly backup verification',
91+
description: 'Verify that all systems are backing up correctly and data integrity is maintained',
92+
dueDate: new Date(), // Today
93+
dueTime: '09:00',
94+
colleague: mockColleagues[4],
95+
isCompleted: false,
96+
isRecurring: true,
97+
recurrencePattern: 'monthly',
98+
recurrenceInterval: 1,
99+
priority: 'medium',
100+
reminderEnabled: true,
101+
reminderMinutes: 1440, // 1 day before
102+
createdAt: new Date(2024, 1, 15),
103+
tags: ['backup', 'verification', 'monthly', 'system']
104+
},
105+
{
106+
id: '6',
107+
title: 'Design review session',
108+
description: 'Review the latest mockups and prototypes with the design team',
109+
dueDate: new Date(Date.now() + 24 * 60 * 60 * 1000), // Tomorrow
110+
dueTime: '11:30',
111+
colleague: mockColleagues[1],
112+
isCompleted: false,
113+
isRecurring: false,
114+
priority: 'medium',
115+
reminderEnabled: true,
116+
reminderMinutes: 30,
117+
createdAt: new Date(),
118+
tags: ['design', 'review', 'mockups']
119+
}
120+
];
121+
122+
export const Default: Story = {
123+
args: {
124+
initialReminders: mockReminders,
125+
initialColleagues: mockColleagues,
126+
onAddReminder: action('onAddReminder'),
127+
onUpdateReminder: action('onUpdateReminder'),
128+
onDeleteReminder: action('onDeleteReminder'),
129+
},
130+
};
131+
132+
export const Empty: Story = {
133+
args: {
134+
initialReminders: [],
135+
initialColleagues: mockColleagues,
136+
onAddReminder: action('onAddReminder'),
137+
onUpdateReminder: action('onUpdateReminder'),
138+
onDeleteReminder: action('onDeleteReminder'),
139+
},
140+
};
141+
142+
export const CompletedOnly: Story = {
143+
args: {
144+
initialReminders: mockReminders.filter(r => r.isCompleted),
145+
initialColleagues: mockColleagues,
146+
onAddReminder: action('onAddReminder'),
147+
onUpdateReminder: action('onUpdateReminder'),
148+
onDeleteReminder: action('onDeleteReminder'),
149+
},
150+
};
151+
152+
export const OverdueOnly: Story = {
153+
args: {
154+
initialReminders: mockReminders.filter(r => {
155+
const now = new Date();
156+
const due = new Date(r.dueDate);
157+
if (r.dueTime) {
158+
const [hours, minutes] = r.dueTime.split(':');
159+
due.setHours(parseInt(hours), parseInt(minutes));
160+
}
161+
return due < now && !r.isCompleted;
162+
}),
163+
initialColleagues: mockColleagues,
164+
onAddReminder: action('onAddReminder'),
165+
onUpdateReminder: action('onUpdateReminder'),
166+
onDeleteReminder: action('onDeleteReminder'),
167+
},
168+
};
169+
170+
export const HighPriorityOnly: Story = {
171+
args: {
172+
initialReminders: mockReminders.filter(r => r.priority === 'high'),
173+
initialColleagues: mockColleagues,
174+
onAddReminder: action('onAddReminder'),
175+
onUpdateReminder: action('onUpdateReminder'),
176+
onDeleteReminder: action('onDeleteReminder'),
177+
},
178+
};
179+
180+
export const RecurringOnly: Story = {
181+
args: {
182+
initialReminders: mockReminders.filter(r => r.isRecurring),
183+
initialColleagues: mockColleagues,
184+
onAddReminder: action('onAddReminder'),
185+
onUpdateReminder: action('onUpdateReminder'),
186+
onDeleteReminder: action('onDeleteReminder'),
187+
},
188+
};
189+
190+
export const WithInteractions: Story = {
191+
args: {
192+
initialReminders: mockReminders,
193+
initialColleagues: mockColleagues,
194+
onAddReminder: action('onAddReminder'),
195+
onUpdateReminder: action('onUpdateReminder'),
196+
onDeleteReminder: action('onDeleteReminder'),
197+
},
198+
};
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import React from 'react';
2+
import { render, screen, fireEvent } from '@testing-library/react';
3+
import '@testing-library/jest-dom';
4+
import { TasksView } from './TasksView';
5+
import type { Reminder, DigitalColleague } from './TasksView';
6+
7+
// Mock colleagues for testing
8+
const mockColleagues: DigitalColleague[] = [
9+
{ id: '1', name: 'Alex AI', department: 'Development', role: 'Senior Developer' },
10+
{ id: '2', name: 'Maya Bot', department: 'Design', role: 'UX Designer' },
11+
];
12+
13+
// Mock reminders for testing
14+
const mockReminders: Reminder[] = [
15+
{
16+
id: '1',
17+
title: 'Test reminder',
18+
description: 'This is a test reminder',
19+
dueDate: new Date(2024, 2, 15),
20+
dueTime: '14:30',
21+
colleague: mockColleagues[0],
22+
isCompleted: false,
23+
isRecurring: false,
24+
priority: 'high',
25+
reminderEnabled: true,
26+
reminderMinutes: 30,
27+
createdAt: new Date(2024, 2, 10),
28+
tags: ['test']
29+
},
30+
];
31+
32+
describe('TasksView', () => {
33+
it('renders the TasksView component', () => {
34+
render(
35+
<TasksView
36+
initialReminders={mockReminders}
37+
initialColleagues={mockColleagues}
38+
/>
39+
);
40+
41+
expect(screen.getByText('Task Reminders')).toBeInTheDocument();
42+
expect(screen.getByText('Test reminder')).toBeInTheDocument();
43+
});
44+
45+
it('displays stats correctly', () => {
46+
render(
47+
<TasksView
48+
initialReminders={mockReminders}
49+
initialColleagues={mockColleagues}
50+
/>
51+
);
52+
53+
expect(screen.getByText('1')).toBeInTheDocument(); // Total count
54+
expect(screen.getByText('Total')).toBeInTheDocument();
55+
expect(screen.getByText('Pending')).toBeInTheDocument();
56+
});
57+
58+
it('can toggle reminder completion', () => {
59+
const mockOnUpdate = jest.fn();
60+
61+
render(
62+
<TasksView
63+
initialReminders={mockReminders}
64+
initialColleagues={mockColleagues}
65+
onUpdateReminder={mockOnUpdate}
66+
/>
67+
);
68+
69+
const checkButton = screen.getByRole('button', { name: /toggle complete/i });
70+
fireEvent.click(checkButton);
71+
72+
expect(mockOnUpdate).toHaveBeenCalledWith('1', {
73+
isCompleted: true,
74+
completedAt: expect.any(Date)
75+
});
76+
});
77+
78+
it('can filter reminders', () => {
79+
render(
80+
<TasksView
81+
initialReminders={mockReminders}
82+
initialColleagues={mockColleagues}
83+
/>
84+
);
85+
86+
const filterSelect = screen.getByRole('combobox');
87+
fireEvent.click(filterSelect);
88+
89+
expect(screen.getByText('All (1)')).toBeInTheDocument();
90+
expect(screen.getByText('Pending (1)')).toBeInTheDocument();
91+
});
92+
93+
it('can search reminders', () => {
94+
render(
95+
<TasksView
96+
initialReminders={mockReminders}
97+
initialColleagues={mockColleagues}
98+
/>
99+
);
100+
101+
const searchInput = screen.getByPlaceholderText('Search reminders...');
102+
fireEvent.change(searchInput, { target: { value: 'test' } });
103+
104+
expect(screen.getByText('Test reminder')).toBeInTheDocument();
105+
});
106+
107+
it('shows empty state when no reminders', () => {
108+
render(
109+
<TasksView
110+
initialReminders={[]}
111+
initialColleagues={mockColleagues}
112+
/>
113+
);
114+
115+
expect(screen.getByText('No reminders yet')).toBeInTheDocument();
116+
expect(screen.getByText('Create your first reminder to get started')).toBeInTheDocument();
117+
});
118+
});

0 commit comments

Comments
 (0)