Skip to content

Commit fa4728d

Browse files
committed
Add member profile page and enhance user navigation
- Implemented MemberProfile component to display user details. - Integrated user fetching logic based on user ID from URL parameters. - Updated routing to include member profile navigation. - Enhanced task list and project pages with improved user interaction.
1 parent 063a9ae commit fa4728d

11 files changed

Lines changed: 270 additions & 25 deletions

File tree

src/components/auth/Register.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,7 @@ export default function Register() {
617617
<Text className={styles.errorText}>{errors.birthDate.message}</Text>
618618
)}
619619
</div>
620+
620621
</div>
621622
</div>
622623
);

src/components/dialogs/CreateMainTaskDialog.tsx

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,27 @@ export interface CreateMainTaskDialogProps {
6060
onSubTaskCreated?: () => void;
6161
}
6262

63+
// Helper to convert YYYY-MM-DD string or ISO string to Date object
64+
function parseFormDate(dateStr: string): Date | undefined {
65+
if (!dateStr) return undefined;
66+
// If already an ISO string (contains 'T'), parse directly
67+
if (dateStr.includes('T')) {
68+
const date = new Date(dateStr);
69+
return isNaN(date.getTime()) ? undefined : date;
70+
}
71+
// Otherwise, assume YYYY-MM-DD format
72+
const date = new Date(dateStr + 'T00:00:00');
73+
return isNaN(date.getTime()) ? undefined : date;
74+
}
75+
76+
// Helper to convert Date to YYYY-MM-DD string using local timezone
77+
function formatDateToString(date: Date): string {
78+
const year = date.getFullYear();
79+
const month = String(date.getMonth() + 1).padStart(2, '0');
80+
const day = String(date.getDate()).padStart(2, '0');
81+
return `${year}-${month}-${day}`;
82+
}
83+
6384
export default function CreateMainTaskDialog({
6485
open,
6586
onOpenChange,
@@ -163,6 +184,14 @@ export default function CreateMainTaskDialog({
163184
}
164185
}
165186

187+
function handleStartDateSelect(date: Date): void {
188+
onDateChange('startDate', formatDateToString(date));
189+
}
190+
191+
function handleEndDateSelect(date: Date): void {
192+
onDateChange('endDate', formatDateToString(date));
193+
}
194+
166195
const handleAddSubTask = () => {
167196
if (!mainTaskId) {
168197
alert('Please create the Main Task first before adding SubTasks');
@@ -345,17 +374,25 @@ export default function CreateMainTaskDialog({
345374
<Field label="Start Date" style={{ flex: '1 1 200px' }}>
346375
<DatePicker
347376
placeholder="mm/dd/yyyy"
348-
value={form.startDate ? new Date(form.startDate) : null}
349-
onSelectDate={(date) => onDateChange('startDate', date ? date.toISOString().split('T')[0] : '')}
377+
value={parseFormDate(form.startDate)}
378+
onSelectDate={(date) => {
379+
if (date) {
380+
handleStartDateSelect(date);
381+
}
382+
}}
350383
size="medium"
351384
style={{ width: '100%' }}
352385
/>
353386
</Field>
354387
<Field label="End Date" style={{ flex: '1 1 200px' }}>
355388
<DatePicker
356389
placeholder="mm/dd/yyyy"
357-
value={form.endDate ? new Date(form.endDate) : null}
358-
onSelectDate={(date) => onDateChange('endDate', date ? date.toISOString().split('T')[0] : '')}
390+
value={parseFormDate(form.endDate)}
391+
onSelectDate={(date) => {
392+
if (date) {
393+
handleEndDateSelect(date);
394+
}
395+
}}
359396
size="medium"
360397
style={{ width: '100%' }}
361398
/>

src/components/kanban/KanbanBoard.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,11 @@ export default function KanbanBoard({ projectId }: KanbanBoardProps) {
151151
.filter(t => !t.categoryId)
152152
.map(mapTaskResponse);
153153

154-
const cols = uncategorized.length
155-
? [{ id: 'uncategorized', title: 'Uncategorized', tasks: uncategorized }, ...columnsFromCategories]
156-
: [...columnsFromCategories];
154+
// Always show uncategorized column, even if empty
155+
const cols = [
156+
{ id: 'uncategorized', title: 'Uncategorized', tasks: uncategorized },
157+
...columnsFromCategories
158+
];
157159

158160
setColumns(cols);
159161
})

src/components/kanban/KanbanColumn.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,11 @@ export default function KanbanColumn({ column, onTaskClick, onDeleteColumn, onUp
112112
appearance="transparent"
113113
icon={<Delete20Regular />}
114114
size="small"
115-
onClick={() => onDeleteColumn(column.id)}
115+
onClick={() => {
116+
if (window.confirm(`Are you sure you want to delete the category "${column.title}"? `)) {
117+
onDeleteColumn(column.id);
118+
}
119+
}}
116120
/>
117121
)}
118122
</div>

src/components/sidebar/ProjectList.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const useProjectListStyles = makeStyles({
1616
overflowX: "hidden",
1717
display: "flex",
1818
flexDirection: "column",
19-
gap: tokens.spacingVerticalXS,
2019
},
2120
sectionHeader: {
2221
display: 'flex',
@@ -29,7 +28,7 @@ const useProjectListStyles = makeStyles({
2928
paddingBlock: tokens.spacingVerticalS,
3029
display: 'flex',
3130
alignItems: 'center',
32-
gap: tokens.spacingHorizontalS,
31+
background: tokens.colorNeutralBackground2,
3332
},
3433
navItemLabel: {
3534
overflow: 'hidden',
@@ -39,8 +38,9 @@ const useProjectListStyles = makeStyles({
3938
maxWidth: '100%'
4039
},
4140
selectedItem: {
42-
backgroundColor: tokens.colorNeutralBackground3,
43-
borderRadius: tokens.borderRadiusMedium,
41+
backgroundColor: tokens.colorNeutralBackground1Selected,
42+
color: tokens.colorBrandBackground,
43+
fontWeight: tokens.fontWeightBold
4444
},
4545
});
4646

src/layout/UserLayout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default function UserLayout() {
2121
const isProjectRoot = normalizedPath === "/home/project";
2222
const isProjectWithName = /^\/home\/project\/[^/]+(\/.*)?$/.test(normalizedPath);
2323
const isCreate = normalizedPath === "/home/create";
24+
const isMemberProfile = normalizedPath === "/home/member";
2425

2526
return (
2627
<main className={mergeClasses(s.userLayout,
@@ -32,7 +33,7 @@ export default function UserLayout() {
3233

3334
<section className={mergeClasses(s.contentsLayout)}>
3435

35-
{!isProfile && !isProjectRoot && !isCreate && (
36+
{!isProfile && !isProjectRoot && !isCreate && !isMemberProfile && (
3637
<div className={s.largeGap}>
3738
{isHome ? (
3839
<HomeHeader />
@@ -43,9 +44,9 @@ export default function UserLayout() {
4344
<NavigationHeader />
4445
</>
4546
)}
46-
4747
</div>
4848
)}
49+
4950
<Outlet context={{ bumpProjects: () => setProjectsRefreshCounter((c: number) => c + 1), projectsRefreshCounter }} />
5051
</section >
5152
</main >

src/pages/project/ProjectListPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export default function ProjectListPage() {
6868
<Label>Projects List</Label>
6969
</div>
7070
<div className={mergeClasses(styles.actionsRight, styles.alignCenter)}>
71-
<Button appearance="primary" onClick={() => navigate('/home/project/create')}>
71+
<Button appearance="primary" onClick={() => navigate('/home/create')}>
7272
Create Project
7373
</Button>
7474

src/pages/project/ProjectPage.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -489,12 +489,15 @@ export default function ProjectPage() {
489489
<TableRow>
490490
<TableHeaderCell>Member Name</TableHeaderCell>
491491
<TableHeaderCell>Email</TableHeaderCell>
492-
<TableHeaderCell>Actions</TableHeaderCell>
493492
</TableRow>
494493
</TableHeader>
495494
<TableBody>
496495
{members.map((member) => (
497-
<TableRow key={member.id}>
496+
<TableRow
497+
key={member.id}
498+
style={{ cursor: 'pointer' }}
499+
onClick={() => navigate(`/home/member?userId=${member.id}`)}
500+
>
498501
<TableCell>
499502
<div className={styles.personaRow}>
500503
<Avatar size={32} name={member.displayName} color="colorful" />
@@ -510,7 +513,10 @@ export default function ProjectPage() {
510513
<Button
511514
appearance="secondary"
512515
size="small"
513-
onClick={() => handleRemoveMember(member.id)}
516+
onClick={(e) => {
517+
e.stopPropagation();
518+
handleRemoveMember(member.id);
519+
}}
514520
disabled={actionLoading}
515521
>
516522
Remove Member

src/pages/project/TaskListPage.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,16 +171,15 @@ export default function TaskListPage() {
171171
const findSlug = (name: string) => encodeURIComponent(name.trim().toLowerCase().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-'));
172172
const matched = all.find(p => findSlug(p.projectName) === (titleSlug ? titleSlug.replace(/-/g, '-') : ''));
173173
if (matched) {
174+
console.log('Project matched:', matched, 'ID:', matched.id);
174175
setProject(matched);
175-
// Load categories and users for this project
176+
// Load categories for this project
176177
if (matched.id) {
177178
setIsLoadingCategories(true);
178179
categoriesApi.getCategoriesByProject(matched.id)
179180
.then(cats => { if (active) setCategories(cats); })
180181
.catch(err => console.error('Failed to load categories:', err))
181182
.finally(() => { if (active) setIsLoadingCategories(false); });
182-
183-
fetchAssignableUsers();
184183
}
185184
} else {
186185
setError('Project not found');
@@ -197,6 +196,15 @@ export default function TaskListPage() {
197196
// eslint-disable-next-line react-hooks/exhaustive-deps
198197
}, [titleSlug]);
199198

199+
// Fetch assignable users when project is loaded
200+
useEffect(() => {
201+
if (project?.id) {
202+
console.log('Project state updated, fetching assignable users with ID:', project.id);
203+
fetchAssignableUsers();
204+
}
205+
// eslint-disable-next-line react-hooks/exhaustive-deps
206+
}, [project?.id]);
207+
200208
useEffect(() => {
201209
if (project?.id) {
202210
loadMainTasks();
@@ -353,9 +361,13 @@ export default function TaskListPage() {
353361
try {
354362
let unique: User[] = [];
355363

364+
console.log('fetchAssignableUsers started, project.id:', project?.id);
365+
356366
if (project?.id) {
357367
try {
358368
const projectMembers = await projectsApi.getProjectMembers(project.id);
369+
console.log('Project members fetched:', projectMembers);
370+
359371
unique = projectMembers.map(member => ({
360372
id: member.id,
361373
userName: member.userName,
@@ -365,17 +377,22 @@ export default function TaskListPage() {
365377
email: member.email,
366378
userIMG: member.userIMG,
367379
} as User));
380+
console.log('Project members mapped to User objects:', unique);
368381
} catch (projectErr) {
369382
console.error('Failed to fetch project members:', projectErr);
370383
}
384+
} else {
385+
console.warn('No project ID available');
371386
}
372387

373-
if (user && !unique.some((u) => u.id === user.id)) unique.push(user);
388+
console.log('Final assignable users list:', unique);
374389
setAssignableUsers(unique);
390+
console.log('setAssignableUsers state updated');
375391
} catch (err) {
376392
console.error('Failed to fetch users:', err);
377393
} finally {
378394
setIsLoadingAssignableUsers(false);
395+
console.log('fetchAssignableUsers completed, loading state set to false');
379396
}
380397
}
381398

0 commit comments

Comments
 (0)