Skip to content

Commit c2cf9b0

Browse files
authored
Connect Weekly Earnings to Frontend (#303)
1 parent ba2c6f3 commit c2cf9b0

File tree

4 files changed

+115
-59
lines changed

4 files changed

+115
-59
lines changed

backend/resolvers/participantResolver.ts

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,38 +120,44 @@ const participantResolver = {
120120
{ participant_id }: { participant_id: number }
121121
): Promise<number[]> => {
122122
const today = new Date();
123-
const sevenDaysAgo = new Date();
124-
sevenDaysAgo.setDate(today.getDate() - 6);
123+
124+
const dayOfWeek = today.getDay();
125+
const mondayOffset = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
126+
127+
const monday = new Date(today);
128+
monday.setDate(today.getDate() - mondayOffset);
129+
monday.setHours(0, 0, 0, 0);
125130

126131
const todayStr = today.toISOString().split("T")[0];
127-
const startStr = sevenDaysAgo.toISOString().split("T")[0];
132+
const mondayStr = monday.toISOString().split("T")[0];
128133

129134
const results = await prisma.$queryRaw<
130135
{ transaction_date: string; total: number }[]
131136
>`
132-
SELECT
137+
SELECT
133138
transaction_date,
134139
SUM(marillac_bucks) AS total
135140
FROM transaction
136141
WHERE participant_id = ${participant_id}
137142
AND transaction_type = 'EARNING'
138-
AND transaction_date >= ${startStr}
143+
AND transaction_date >= ${mondayStr}
139144
AND transaction_date <= ${todayStr}
140145
GROUP BY transaction_date
141146
ORDER BY transaction_date ASC
142147
`;
143148

144-
const last7Days: string[] = Array.from({ length: 7 }, (_, i) => {
145-
const d = new Date();
146-
d.setDate(today.getDate() - (6 - i));
149+
// Create array for Monday through Sunday (7 days)
150+
const weekDays: string[] = Array.from({ length: 7 }, (_, i) => {
151+
const d = new Date(monday);
152+
d.setDate(monday.getDate() + i);
147153
return d.toISOString().split("T")[0];
148154
});
149155

150156
const earningsMap = Object.fromEntries(
151157
results.map((r) => [r.transaction_date, Number(r.total)])
152158
);
153159

154-
return last7Days.map((day) => earningsMap[day] || 0);
160+
return weekDays.map((day) => earningsMap[day] || 0);
155161
},
156162
},
157163
Mutation: {
@@ -343,6 +349,53 @@ const participantResolver = {
343349
VALUES (${participant_id}, 'SET', ${new_goal_value}, ${getToday()})
344350
`;
345351

352+
return true;
353+
},
354+
createEarningTransaction: async (
355+
_parent: undefined,
356+
{
357+
participant_id,
358+
transaction_date,
359+
marillac_bucks,
360+
description,
361+
}: {
362+
participant_id: number;
363+
transaction_date: string;
364+
marillac_bucks: number;
365+
description?: string;
366+
}
367+
): Promise<boolean> => {
368+
if (marillac_bucks <= 0) {
369+
throw new Error("Marillac bucks must be greater than 0");
370+
}
371+
372+
const participant = await prisma.participant.findUnique({
373+
where: { participant_id },
374+
});
375+
376+
if (!participant) {
377+
throw new Error("Participant not found");
378+
}
379+
380+
await prisma.transaction.create({
381+
data: {
382+
participant_id,
383+
transaction_date,
384+
transaction_type: "EARNING",
385+
marillac_bucks,
386+
description: description || "Daily earnings",
387+
},
388+
});
389+
390+
const newBalance = participant.marillac_bucks + marillac_bucks;
391+
392+
await prisma.participant.update({
393+
where: { participant_id },
394+
data: { marillac_bucks: newBalance },
395+
});
396+
397+
await checkAndRecordGoalReached(participant_id);
398+
346399
return true;
347400
},
348401
},

backend/seed/.snaplet/dataModel.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -874,10 +874,10 @@
874874
"maxLength": null
875875
},
876876
{
877-
"id": "public.goal_history.goal_value",
878-
"name": "goal_value",
879-
"columnName": "goal_value",
880-
"type": "int4",
877+
"id": "public.goal_history.goal_action",
878+
"name": "goal_action",
879+
"columnName": "goal_action",
880+
"type": "GoalAction",
881881
"isRequired": true,
882882
"kind": "scalar",
883883
"isList": false,
@@ -888,10 +888,10 @@
888888
"maxLength": null
889889
},
890890
{
891-
"id": "public.goal_history.action_date",
892-
"name": "action_date",
893-
"columnName": "action_date",
894-
"type": "text",
891+
"id": "public.goal_history.goal_value",
892+
"name": "goal_value",
893+
"columnName": "goal_value",
894+
"type": "int4",
895895
"isRequired": true,
896896
"kind": "scalar",
897897
"isList": false,
@@ -902,10 +902,10 @@
902902
"maxLength": null
903903
},
904904
{
905-
"id": "public.goal_history.goal_action",
906-
"name": "goal_action",
907-
"columnName": "goal_action",
908-
"type": "GoalAction",
905+
"id": "public.goal_history.action_date",
906+
"name": "action_date",
907+
"columnName": "action_date",
908+
"type": "text",
909909
"isRequired": true,
910910
"kind": "scalar",
911911
"isList": false,

backend/types/resolvers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ const resolvers = gql`
169169
monthly: Boolean
170170
): Boolean
171171
deleteReportRecipient(report_recipient_id: Int!): Boolean
172+
createEarningTransaction(
173+
participant_id: Int!
174+
transaction_date: String!
175+
marillac_bucks: Int!
176+
description: String
177+
): Boolean
172178
}
173179
`;
174180

frontend/src/participant/pages/progress/components/EarningsWidget.tsx

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useState } from "react";
22
import {
33
BarChart,
44
Bar,
@@ -23,28 +23,36 @@ interface WeeklyEarningsChartProps {
2323

2424
interface ChartDataItem {
2525
amount: number;
26+
day: string;
2627
isToday: boolean;
2728
}
2829

2930
const WeeklyEarningsChart = ({
3031
weeklyEarnings = [],
3132
}: WeeklyEarningsChartProps) => {
32-
const chartData: ChartDataItem[] = weeklyEarnings.map(
33-
(earnings = 0, index) => ({
34-
amount: earnings,
35-
isToday: index === 6,
36-
})
37-
);
33+
const [selectedBarIndex, setSelectedBarIndex] = useState<number | null>(null);
3834

39-
const maxEarnings = Math.max(...weeklyEarnings.filter((val) => val != null));
40-
const upperBound = Math.ceil(maxEarnings * 1.2);
35+
// Day labels for the week (Monday = index 0, Sunday = index 6)
36+
const allDayLabels = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
4137

42-
const thisWeekTotal = weeklyEarnings.reduce((sum, val = 0) => sum + val, 0);
43-
// will need to turn this into a parameter:
44-
const lastWeekTotal = thisWeekTotal * 0.77;
45-
const percentageChange = Math.round(
46-
((thisWeekTotal - lastWeekTotal) / lastWeekTotal) * 100
47-
);
38+
// Calculate which day of the week today is (0 = Monday, 6 = Sunday)
39+
const today = new Date();
40+
const dayOfWeek = today.getDay(); // 0 = Sunday, 1 = Monday, etc.
41+
const todayIndex = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Convert to 0 = Monday
42+
43+
// Only show days from Monday up to today
44+
const chartData: ChartDataItem[] = weeklyEarnings
45+
.slice(0, todayIndex + 1) // Only include Monday through today
46+
.map((earnings = 0, index) => ({
47+
amount: earnings,
48+
day: allDayLabels[index] || "",
49+
isToday: index === todayIndex,
50+
}));
51+
52+
// Calculate max earnings from only the days we're showing
53+
const visibleEarnings = weeklyEarnings.slice(0, todayIndex + 1);
54+
const maxEarnings = Math.max(...visibleEarnings.filter((val) => val != null), 0);
55+
const upperBound = Math.ceil(maxEarnings * 1.2) || 10;
4856

4957
return (
5058
<div
@@ -82,10 +90,10 @@ const WeeklyEarningsChart = ({
8290
vertical={false}
8391
/>
8492
<XAxis
85-
dataKey="amount"
93+
dataKey="day"
8694
axisLine={false}
8795
tickLine={false}
88-
tick={false}
96+
tick={{ fill: colors.text.light.secondary, fontSize: 12 }}
8997
/>
9098
<YAxis
9199
domain={[0, upperBound]}
@@ -95,7 +103,14 @@ const WeeklyEarningsChart = ({
95103
tickFormatter={(value) => `$${value}`}
96104
width={30}
97105
/>
98-
<Bar dataKey="amount" radius={[2, 2, 0, 0]}>
106+
<Bar
107+
dataKey="amount"
108+
radius={[2, 2, 0, 0]}
109+
onClick={(data: any, index: number) => {
110+
setSelectedBarIndex(index === selectedBarIndex ? null : index);
111+
}}
112+
style={{ cursor: "pointer" }}
113+
>
99114
{chartData.map((entry, index) => (
100115
<Cell
101116
key={`cell-${index}`}
@@ -109,7 +124,8 @@ const WeeklyEarningsChart = ({
109124
content={(props: any) => {
110125
const { x, y, value, index } = props;
111126
if (
112-
index === chartData.length - 1 &&
127+
selectedBarIndex !== null &&
128+
index === selectedBarIndex &&
113129
x !== undefined &&
114130
y !== undefined &&
115131
value !== undefined
@@ -134,25 +150,6 @@ const WeeklyEarningsChart = ({
134150
</BarChart>
135151
</ResponsiveContainer>
136152
</div>
137-
138-
<div
139-
style={{
140-
display: "flex",
141-
alignItems: "center",
142-
justifyContent: "center",
143-
gap: 8,
144-
}}
145-
>
146-
<span
147-
style={{
148-
color: colors.success[900],
149-
fontSize: 18,
150-
fontWeight: "bold",
151-
}}
152-
>
153-
{percentageChange}% Compared to Last Week
154-
</span>
155-
</div>
156153
</div>
157154
);
158155
};

0 commit comments

Comments
 (0)