Skip to content

Commit 2ec3855

Browse files
committed
feat(projects): enhance project status display with capitalization and badge variants
1 parent cd854aa commit 2ec3855

4 files changed

Lines changed: 69 additions & 6 deletions

File tree

src/components/projects/project-detail-page.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
.statsGrid {
3434
display: grid;
35-
grid-template-columns: repeat(3, 1fr);
35+
grid-template-columns: repeat(4, 1fr);
3636
gap: 12px;
3737
margin-top: 8px;
3838
}

src/components/projects/project-detail-page.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useMemo, useState } from "react";
22
import { useParams, Link } from "react-router-dom";
3-
import { ArrowLeft, Clock, DollarSign, Calendar, Plus, Pencil, Trash2 } from "lucide-react";
3+
import { ArrowLeft, Clock, DollarSign, Calendar, Plus, Pencil, Trash2, CheckCircle2 } from "lucide-react";
44
import { Button } from "@/components/ui/button";
55
import { Card, CardContent } from "@/components/ui/card";
66
import { Badge } from "@/components/ui/badge";
@@ -21,6 +21,24 @@ function formatDuration(seconds: number): string {
2121
return "0m";
2222
}
2323

24+
function capitalizeStatus(status: string): string {
25+
return status.charAt(0).toUpperCase() + status.slice(1);
26+
}
27+
28+
function getStatusBadgeVariant(status: string): "default" | "secondary" | "destructive" | "outline" {
29+
switch (status) {
30+
case "active":
31+
return "default";
32+
case "completed":
33+
return "outline";
34+
case "cancelled":
35+
return "destructive";
36+
case "paused":
37+
default:
38+
return "secondary";
39+
}
40+
}
41+
2442
export function ProjectDetailPage() {
2543
const { id } = useParams<{ id: string }>();
2644
const { projects, sessions, clients, getClientById, addSession, updateSession, deleteSession } = useAppData();
@@ -49,6 +67,16 @@ export function ProjectDetailPage() {
4967
return project.fixed_budget || 0;
5068
}, [project, totalSeconds]);
5169

70+
const totalPaidEarned = useMemo(() => {
71+
if (!project) return 0;
72+
if (project.billing_type === "hourly" && project.rate) {
73+
return projectSessions
74+
.filter((s) => s.payment_status === "paid")
75+
.reduce((sum, s) => sum + ((s.duration_seconds / 3600) * project.rate!), 0);
76+
}
77+
return project.fixed_budget || 0;
78+
}, [project, projectSessions]);
79+
5280
if (!project) {
5381
return (
5482
<div className={sh.page}>
@@ -71,7 +99,7 @@ export function ProjectDetailPage() {
7199
<div>
72100
<h1 className={s.projectName}>{project.name}</h1>
73101
<p className={s.meta}>
74-
{client?.name || "—"} · <Badge variant="secondary">{project.status}</Badge> · {project.billing_type === "hourly" ? `$${project.rate}/hr` : `$${project.fixed_budget} fixed`}
102+
{client?.name || "—"} · <Badge variant={getStatusBadgeVariant(project.status)}>{capitalizeStatus(project.status)}</Badge> · {project.billing_type === "hourly" ? `$${project.rate}/hr` : `$${project.fixed_budget} fixed`}
75103
</p>
76104
</div>
77105
<div className={s.headerActions}>
@@ -103,6 +131,15 @@ export function ProjectDetailPage() {
103131
</div>
104132
</CardContent>
105133
</Card>
134+
<Card>
135+
<CardContent className={s.statCard}>
136+
<CheckCircle2 className={s.statIcon} style={{ color: "var(--green-500)" }} />
137+
<div>
138+
<p className={s.statValue}>${totalPaidEarned.toFixed(0)}</p>
139+
<p className={s.statLabel}>{t("invoices.paid")}</p>
140+
</div>
141+
</CardContent>
142+
</Card>
106143
<Card>
107144
<CardContent className={s.statCard}>
108145
<Calendar className={s.statIcon} style={{ color: "var(--violet-400)" }} />

src/components/projects/projects-page.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
import { useState } from "react";
22
import { useNavigate } from "react-router-dom";
33
import { Plus, Pencil, Trash2 } from "lucide-react";
4+
5+
function capitalizeStatus(status: string): string {
6+
return status.charAt(0).toUpperCase() + status.slice(1);
7+
}
8+
9+
function getStatusBadgeVariant(status: string): "default" | "secondary" | "destructive" | "outline" {
10+
switch (status) {
11+
case "active":
12+
return "default";
13+
case "completed":
14+
return "outline";
15+
case "cancelled":
16+
return "destructive";
17+
case "paused":
18+
default:
19+
return "secondary";
20+
}
21+
}
422
import { Button } from "@/components/ui/button";
523
import { Badge } from "@/components/ui/badge";
624
import { Dialog } from "@/components/ui/dialog";
@@ -49,6 +67,7 @@ export function ProjectsPage() {
4967
<th>{t("projects.rate")}</th>
5068
<th>{t("reports.totalTime")}</th>
5169
<th>{t("reports.earnings")}</th>
70+
<th>{t("invoices.paid")}</th>
5271
<th></th>
5372
</tr>
5473
</thead>
@@ -61,16 +80,22 @@ export function ProjectsPage() {
6180
const earned = project.billing_type === "hourly"
6281
? (totalSeconds / 3600) * (project.rate || 0)
6382
: (project.fixed_budget || 0);
83+
const paidEarned = project.billing_type === "hourly"
84+
? projectSessions
85+
.filter((s) => s.payment_status === "paid")
86+
.reduce((sum, s) => sum + ((s.duration_seconds / 3600) * (project.rate || 0)), 0)
87+
: (project.fixed_budget || 0);
6488

6589
return (
6690
<tr key={project.id} onClick={() => navigate(`/app/projects/${project.id}`)} style={{ cursor: "pointer" }}>
6791
<td className={s.nameCell}>{project.name}</td>
6892
<td className={s.mutedCell}>{client?.name || "—"}</td>
69-
<td><Badge variant="secondary">{project.status}</Badge></td>
93+
<td><Badge variant={getStatusBadgeVariant(project.status)}>{capitalizeStatus(project.status)}</Badge></td>
7094
<td className={s.mutedCell}>{project.billing_type}</td>
7195
<td className={s.mutedCell}>{project.rate ? `$${project.rate}/hr` : "—"}</td>
7296
<td className={s.mutedCell}>{hours}h</td>
7397
<td className={s.mutedCell}>{currencySymbol}{earned.toFixed(0)}</td>
98+
<td className={s.mutedCell}>{currencySymbol}{paidEarned.toFixed(0)}</td>
7499
<td className={s.actionsCell}>
75100
<button className={s.actionBtn} onClick={(e) => { e.stopPropagation(); setEditingId(project.id); }}><Pencil style={{ width: 14, height: 14 }} /></button>
76101
<button className={s.actionBtn} onClick={(e) => { e.stopPropagation(); deleteProject(project.id); }}><Trash2 style={{ width: 14, height: 14 }} /></button>

src/components/ui/badge.module.css

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
}
1414

1515
.default {
16-
background: var(--black);
17-
color: var(--white);
16+
background: var(--emerald-50);
17+
border-color: var(--emerald-200);
18+
color: var(--emerald-700);
1819
}
1920

2021
.secondary {

0 commit comments

Comments
 (0)