Skip to content

Commit 4b80870

Browse files
committed
added a timeline feature for the team member project dashboard
1 parent ec20de7 commit 4b80870

File tree

3 files changed

+186
-25
lines changed

3 files changed

+186
-25
lines changed

.DS_Store

8 KB
Binary file not shown.
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { Box, Text, Flex, Badge } from "@chakra-ui/react"
2+
import { type ProjectPublic } from "@/client"
3+
4+
interface ProjectTimelineProps {
5+
project: ProjectPublic
6+
}
7+
8+
export function ProjectTimeline({ project }: ProjectTimelineProps) {
9+
const startDate = project.start_date ? new Date(project.start_date) : null
10+
const deadline = project.deadline ? new Date(project.deadline) : null
11+
const today = new Date()
12+
13+
// Calculate progress based on dates
14+
const getDateProgress = () => {
15+
if (!startDate || !deadline) return 0
16+
const total = deadline.getTime() - startDate.getTime()
17+
const elapsed = today.getTime() - startDate.getTime()
18+
return Math.max(0, Math.min(100, (elapsed / total) * 100))
19+
}
20+
21+
const dateProgress = getDateProgress()
22+
const isOverdue = deadline && today > deadline
23+
const daysRemaining = deadline
24+
? Math.ceil((deadline.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
25+
: null
26+
27+
// Define milestones based on project phases
28+
const milestones = [
29+
{ label: "Start", date: startDate, position: 0 },
30+
{ label: "25%", date: null, position: 25 },
31+
{ label: "50%", date: null, position: 50 },
32+
{ label: "75%", date: null, position: 75 },
33+
{ label: "Deadline", date: deadline, position: 100 },
34+
]
35+
36+
const getStatusColor = () => {
37+
if (isOverdue) return "red.500"
38+
if (dateProgress > 75) return "orange.500"
39+
if (dateProgress > 50) return "yellow.500"
40+
return "green.500"
41+
}
42+
43+
const formatDate = (date: Date | null) => {
44+
if (!date) return "Not set"
45+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
46+
}
47+
48+
return (
49+
<Box width="100%">
50+
{/* Header */}
51+
<Flex justify="space-between" align="center" mb="6">
52+
<Box>
53+
<Text fontSize="lg" fontWeight="bold">Project Timeline</Text>
54+
<Text fontSize="sm" color="fg.muted">
55+
{formatDate(startDate)}{formatDate(deadline)}
56+
</Text>
57+
</Box>
58+
<Box textAlign="right">
59+
{daysRemaining !== null && (
60+
<Badge colorScheme={isOverdue ? "red" : daysRemaining < 7 ? "orange" : "green"}>
61+
{isOverdue ? `${Math.abs(daysRemaining)} days overdue` : `${daysRemaining} days left`}
62+
</Badge>
63+
)}
64+
<Text fontSize="sm" color="fg.muted" mt="1">
65+
Progress: {project.progress || 0}%
66+
</Text>
67+
</Box>
68+
</Flex>
69+
70+
{/* Timeline */}
71+
<Box position="relative" mt="16" mb="16" px="4">
72+
{/* Background track */}
73+
<Box
74+
position="absolute"
75+
top="40px"
76+
left="4"
77+
right="4"
78+
height="8px"
79+
bg="bg.muted"
80+
borderRadius="full"
81+
/>
82+
83+
{/* Progress bar (based on actual progress) */}
84+
<Box
85+
position="absolute"
86+
top="40px"
87+
left="-6"
88+
width={`calc(${project.progress || 0}% - -16px)`}
89+
height="8px"
90+
bg={getStatusColor()}
91+
borderRadius="full"
92+
transition="width 0.3s ease"
93+
/>
94+
95+
{/* Current date indicator (based on time elapsed) */}
96+
{startDate && deadline && (
97+
<Box
98+
position="absolute"
99+
top="24px"
100+
left={`calc(${dateProgress}%)`}
101+
transform="translateX(-75%)"
102+
zIndex="3"
103+
>
104+
<Box
105+
width="2px"
106+
height="24px"
107+
bg="blue.500"
108+
mb="4"
109+
/>
110+
<Text
111+
fontSize="xs"
112+
fontWeight="bold"
113+
color="blue.500"
114+
textAlign="center"
115+
whiteSpace="nowrap"
116+
mt="-1"
117+
>
118+
Today
119+
</Text>
120+
</Box>
121+
)}
122+
123+
{/* Milestones */}
124+
{milestones.map((milestone, index) => (
125+
<Box
126+
key={index}
127+
position="absolute"
128+
top="36px"
129+
left={`${milestone.position}%`}
130+
transform="translateX(-30%)"
131+
zIndex="2"
132+
>
133+
{/* Milestone dot */}
134+
<Box
135+
width="16px"
136+
height="16px"
137+
bg={milestone.position <= (project.progress || 0) ? getStatusColor() : "bg.muted"}
138+
borderRadius="full"
139+
borderWidth="3px"
140+
borderColor="white"
141+
boxShadow="sm"
142+
/>
143+
144+
{/* Milestone label */}
145+
<Text
146+
fontSize="xs"
147+
color="fg.muted"
148+
textAlign="center"
149+
whiteSpace="nowrap"
150+
mt="6"
151+
>
152+
{milestone.label}
153+
</Text>
154+
155+
{/* Date if available */}
156+
{milestone.date && (
157+
<Text
158+
fontSize="xs"
159+
color="fg.subtle"
160+
textAlign="center"
161+
whiteSpace="nowrap"
162+
mt="1"
163+
>
164+
{formatDate(milestone.date)}
165+
</Text>
166+
)}
167+
</Box>
168+
))}
169+
</Box>
170+
171+
{/* Status legend */}
172+
<Flex gap="4" mt="8" fontSize="sm" color="fg.muted" justify="center">
173+
<Flex align="center" gap="2">
174+
<Box width="12px" height="12px" bg={getStatusColor()} borderRadius="full" />
175+
<Text>Current Progress</Text>
176+
</Flex>
177+
<Flex align="center" gap="2">
178+
<Box width="12px" height="12px" bg="blue.500" borderRadius="full" />
179+
<Text>Today</Text>
180+
</Flex>
181+
</Flex>
182+
</Box>
183+
)
184+
}

frontend/src/routes/_layout/projects.$projectId.tsx

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { InviteClient } from "@/components/Projects/InviteClient"
2828
import { ClientAccessList } from "@/components/Projects/ClientAccessList"
2929
import { EditProject } from "@/components/Projects/EditProject"
3030
import { DeleteProject } from "@/components/Projects/DeleteProject"
31+
import { ProjectTimeline } from "@/components/Projects/ProjectTimeline"
3132

3233
export const Route = createFileRoute("/_layout/projects/$projectId")({
3334
component: ProjectDetail,
@@ -249,32 +250,8 @@ function ProjectDetail() {
249250

250251
{/* Timeline / Milestones */}
251252
<Card.Root>
252-
<Card.Header>
253-
<Heading size="lg">Project Progress</Heading>
254-
</Card.Header>
255253
<Card.Body>
256-
<Stack gap={4}>
257-
<Box>
258-
<Text fontWeight="semibold" mb={2}>Status</Text>
259-
<Badge size="lg" colorScheme={getStatusColor(project.status || 'pending')}>
260-
{getStatusLabel(project.status || 'pending')}
261-
</Badge>
262-
</Box>
263-
<Box>
264-
<Text fontWeight="semibold" mb={2}>Completion</Text>
265-
<Flex alignItems="center" gap={3}>
266-
<Box flex={1} h="8px" bg="bg.muted" borderRadius="full" overflow="hidden">
267-
<Box
268-
h="100%"
269-
w={`${project.progress || 0}%`}
270-
bg={(project.progress || 0) === 100 ? "green.500" : (project.progress || 0) >= 50 ? "blue.500" : "orange.500"}
271-
transition="width 0.3s"
272-
/>
273-
</Box>
274-
<Text fontWeight="semibold">{project.progress || 0}%</Text>
275-
</Flex>
276-
</Box>
277-
</Stack>
254+
<ProjectTimeline project={project} />
278255
</Card.Body>
279256
</Card.Root>
280257
</Stack>

0 commit comments

Comments
 (0)