Skip to content

Commit 7dd1f54

Browse files
committed
feat: Add date handling and calendar integration in task dialogs and kanban board
1 parent 5460e1c commit 7dd1f54

6 files changed

Lines changed: 443 additions & 94 deletions

File tree

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13+
"@dnd-kit/core": "^6.3.1",
14+
"@dnd-kit/sortable": "^10.0.0",
15+
"@dnd-kit/utilities": "^3.2.2",
16+
"@fluentui/react-calendar-compat": "^0.3.15",
1317
"@fluentui/react-components": "^9.72.4",
1418
"axios": "^1.13.2",
1519
"react": "19",

src/components/dialogs/TaskDialog.tsx

Lines changed: 206 additions & 43 deletions
Large diffs are not rendered by default.

src/components/kanban/KanbanBoard.tsx

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,38 @@ export default function KanbanBoard({ projectId }: KanbanBoardProps) {
7070

7171
// Load current user
7272
useEffect(() => {
73-
usersApi.getCurrentUser().then(setCurrentUser).catch(() => {});
73+
usersApi.getCurrentUser().then(setCurrentUser).catch(() => { });
7474
}, []);
7575

76-
// Load assignable users (project team members)
76+
// Load assignable users (project team members, with fallback to all users)
7777
useEffect(() => {
7878
if (!projectId) return;
7979
setIsLoadingAssignableUsers(true);
80+
8081
projectsApi.getProjectById(projectId)
81-
.then(project => Promise.all(project.teamMembers.map(id => usersApi.getUserById(id).catch(() => null))))
82-
.then(results => {
83-
setAssignableUsers(results.filter(Boolean) as User[]);
82+
.then(async project => {
83+
const teamMemberIds = project.teamMembers || [];
84+
if (teamMemberIds.length > 0) {
85+
const results = await Promise.all(teamMemberIds.map(id => usersApi.getUserById(id).catch(() => null)));
86+
const members = results.filter(Boolean) as User[];
87+
if (members.length > 0) {
88+
setAssignableUsers(members);
89+
return;
90+
}
91+
}
92+
// Fallback: fetch all users if no team members found
93+
const allUsers = await usersApi.getAllUsers();
94+
setAssignableUsers(allUsers);
95+
})
96+
.catch(async () => {
97+
// On error, try to fetch all users as fallback
98+
try {
99+
const allUsers = await usersApi.getAllUsers();
100+
setAssignableUsers(allUsers);
101+
} catch {
102+
setAssignableUsers([]);
103+
}
84104
})
85-
.catch(() => setAssignableUsers([]))
86105
.finally(() => setIsLoadingAssignableUsers(false));
87106
}, [projectId]);
88107

@@ -148,15 +167,27 @@ export default function KanbanBoard({ projectId }: KanbanBoardProps) {
148167
usersById={usersById}
149168
onDropTask={handleDropTask}
150169
onTaskClick={(task) => {
170+
// Helper to format date for HTML date input (YYYY-MM-DD)
171+
const formatDateForInput = (dateValue: string | null | undefined): string => {
172+
if (!dateValue) return '';
173+
try {
174+
const date = new Date(dateValue);
175+
if (isNaN(date.getTime())) return '';
176+
return date.toISOString().split('T')[0];
177+
} catch {
178+
return '';
179+
}
180+
};
181+
151182
// Open dialog with clicked task
152183
setDialogMode('edit');
153184
setDialogForm({
154185
title: task.title || '',
155186
description: task.description || '',
156187
priority: task.priority || 'Low',
157188
status: task.status || 'To Do',
158-
startDate: task.startDate || '',
159-
endDate: task.endDate || '',
189+
startDate: formatDateForInput(task.startDate),
190+
endDate: formatDateForInput(task.endDate),
160191
assignedTo: task.assignedTo || [],
161192
createdBy: task.createdBy || '',
162193
category: task.category || '',

src/pages/project/TaskListPage.tsx

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ export default function TaskListPage() {
8989
fetchAssignableUsers();
9090
};
9191

92+
// Helper to format date for HTML date input (YYYY-MM-DD)
93+
const formatDateForInput = (dateValue: string | null | undefined): string => {
94+
if (!dateValue) return '';
95+
try {
96+
const date = new Date(dateValue);
97+
if (isNaN(date.getTime())) return '';
98+
return date.toISOString().split('T')[0];
99+
} catch {
100+
return '';
101+
}
102+
};
103+
92104
const onRowClick = (task: Task) => {
93105
setDialogMode('edit');
94106
setEditingTaskId(task._id);
@@ -98,8 +110,8 @@ export default function TaskListPage() {
98110
description: task.description || '',
99111
priority: task.priority || 'Medium',
100112
status: task.status || 'To Do',
101-
startDate: task.startDate || '',
102-
endDate: task.endDate || '',
113+
startDate: formatDateForInput(task.startDate),
114+
endDate: formatDateForInput(task.endDate),
103115
assignedTo: task.assignedTo || [],
104116
createdBy: task.createdBy || '',
105117
category: task.category || task.categoryId || '',
@@ -113,21 +125,36 @@ export default function TaskListPage() {
113125
async function fetchAssignableUsers(assignedToIds?: string[]) {
114126
setIsLoadingAssignableUsers(true);
115127
try {
116-
if (!project?.id) {
117-
setAssignableUsers([]);
118-
return;
119-
}
128+
let unique: User[] = [];
120129

121-
// Fetch the project details to get team members
122-
const projectDetails = await projectsApi.getProjectById(project.id);
123-
const teamMemberIds = projectDetails.teamMembers || [];
130+
if (project?.id) {
131+
try {
132+
// Fetch the project details to get team members
133+
const projectDetails = await projectsApi.getProjectById(project.id);
134+
const teamMemberIds = projectDetails.teamMembers || [];
124135

125-
// Fetch all team member details
126-
const memberPromises = teamMemberIds.map(id =>
127-
usersApi.getUserById(id).catch(() => null)
128-
);
129-
const members = await Promise.all(memberPromises);
130-
const unique = members.filter((m): m is User => m !== null);
136+
if (teamMemberIds.length > 0) {
137+
// Fetch all team member details
138+
const memberPromises = teamMemberIds.map(id =>
139+
usersApi.getUserById(id).catch(() => null)
140+
);
141+
const members = await Promise.all(memberPromises);
142+
unique = members.filter((m): m is User => m !== null);
143+
}
144+
} catch (projectErr) {
145+
console.error('Failed to fetch project details:', projectErr);
146+
}
147+
}
148+
149+
// If no project members were found, fetch all users as fallback
150+
if (unique.length === 0) {
151+
try {
152+
const allUsers = await usersApi.getAllUsers();
153+
unique = allUsers;
154+
} catch (usersErr) {
155+
console.error('Failed to fetch all users:', usersErr);
156+
}
157+
}
131158

132159
// Add current user if not already in the list
133160
if (user && !unique.some((u) => u.id === user.id)) unique.push(user);

src/pages/user/MyTasks.tsx

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -79,43 +79,74 @@ export default function MyTasks() {
7979
setDialogMode('edit');
8080
setEditingTaskId(task._id);
8181
setSelectedTask(task);
82+
83+
// Helper to format date for HTML date input (YYYY-MM-DD)
84+
const formatDateForInput = (dateValue: string | null | undefined): string => {
85+
if (!dateValue) return '';
86+
try {
87+
const date = new Date(dateValue);
88+
if (isNaN(date.getTime())) return '';
89+
return date.toISOString().split('T')[0];
90+
} catch {
91+
return '';
92+
}
93+
};
94+
8295
setForm({
8396
title: task.title || '',
8497
description: task.description || '',
8598
priority: task.priority || 'Medium',
8699
status: task.status || 'To Do',
87-
startDate: task.startDate || '',
88-
endDate: task.endDate || '',
100+
startDate: formatDateForInput(task.startDate),
101+
endDate: formatDateForInput(task.endDate),
89102
assignedTo: task.assignedTo || [],
90103
createdBy: task.createdBy || '',
91104
category: task.category || task.categoryId || '',
92105
projectId: (task as Task & { projectId?: string }).projectId || '',
93106
comments: '',
94107
});
95108
setOpen(true);
96-
fetchAssignableUsers(task.assignedTo);
109+
// Pass the task's projectId directly since setForm is async and form.projectId won't be updated yet
110+
const taskProjectId = (task as Task & { projectId?: string }).projectId || '';
111+
fetchAssignableUsers(task.assignedTo, taskProjectId);
97112
fetchProjects();
98113
}
99114

100-
async function fetchAssignableUsers(assignedToIds?: string[]) {
115+
async function fetchAssignableUsers(assignedToIds?: string[], taskProjectId?: string) {
101116
setIsLoadingAssignableUsers(true);
102117
setAssignableUsersError(null);
103118
try {
104119
let unique: User[] = [];
105120

106121
// If a project is selected, fetch only project members
107-
if (form.projectId || (selectedTask && (selectedTask as Task & { projectId?: string }).projectId)) {
108-
const projectId = form.projectId || (selectedTask && (selectedTask as Task & { projectId?: string }).projectId);
109-
if (projectId) {
122+
// Use taskProjectId if provided (for edit mode), otherwise fall back to form.projectId
123+
const projectId = taskProjectId || form.projectId || (selectedTask && (selectedTask as Task & { projectId?: string }).projectId);
124+
if (projectId) {
125+
try {
110126
const projectDetails = await projectsApi.getProjectById(projectId);
111127
const teamMemberIds = projectDetails.teamMembers || [];
112128

113-
// Fetch all team member details
114-
const memberPromises = teamMemberIds.map(id =>
115-
usersApi.getUserById(id).catch(() => null)
116-
);
117-
const members = await Promise.all(memberPromises);
118-
unique = members.filter((m): m is User => m !== null);
129+
if (teamMemberIds.length > 0) {
130+
// Fetch all team member details
131+
const memberPromises = teamMemberIds.map(id =>
132+
usersApi.getUserById(id).catch(() => null)
133+
);
134+
const members = await Promise.all(memberPromises);
135+
unique = members.filter((m): m is User => m !== null);
136+
}
137+
} catch (projectErr) {
138+
console.error('Failed to fetch project details:', projectErr);
139+
// Fall through to fetch all users
140+
}
141+
}
142+
143+
// If no project members were found, fetch all users as fallback
144+
if (unique.length === 0) {
145+
try {
146+
const allUsers = await usersApi.getAllUsers();
147+
unique = allUsers;
148+
} catch (usersErr) {
149+
console.error('Failed to fetch all users:', usersErr);
119150
}
120151
}
121152

@@ -146,21 +177,21 @@ export default function MyTasks() {
146177
if (value === '' || (Array.isArray(value) && value.length === 0)) return;
147178

148179
try {
149-
// Build a safe typed payload for the patch API
150-
// Build a typed payload object safely (avoid using `any`)
151-
const payloadRec: Record<string, unknown> = {};
152-
if (typeof value === 'string') {
153-
payloadRec[fieldName] = value;
154-
} else if (Array.isArray(value)) {
155-
payloadRec[fieldName] = value as string[];
156-
} else if (typeof value === 'number' || typeof value === 'boolean') {
157-
payloadRec[fieldName] = value;
158-
} else {
159-
// unknown or unsupported type — skip
160-
return;
161-
}
180+
// Build a safe typed payload for the patch API
181+
// Build a typed payload object safely (avoid using `any`)
182+
const payloadRec: Record<string, unknown> = {};
183+
if (typeof value === 'string') {
184+
payloadRec[fieldName] = value;
185+
} else if (Array.isArray(value)) {
186+
payloadRec[fieldName] = value as string[];
187+
} else if (typeof value === 'number' || typeof value === 'boolean') {
188+
payloadRec[fieldName] = value;
189+
} else {
190+
// unknown or unsupported type — skip
191+
return;
192+
}
162193

163-
await tasksApi.patchTask(editingTaskId, payloadRec as Partial<CreateTaskData>);
194+
await tasksApi.patchTask(editingTaskId, payloadRec as Partial<CreateTaskData>);
164195
// refresh data grid
165196
setRefreshKey(k => k + 1);
166197
} catch (err) {

0 commit comments

Comments
 (0)