notification system implemented
need refinements
This commit is contained in:
1 parent
612259f020
commit
1143f8ca02
33 files changed
+2221
-222
No files matched your search
@@ -28,7 +28,7 @@ interface AttendanceStats {
|
||||
thisMonth: number;
|
||||
recentCheckIns: Array<{
|
||||
id: string;
|
||||
checkInTime: string;
|
||||
checkInTime: string; // ISO string
|
||||
checkOutTime: string | null;
|
||||
type: string;
|
||||
duration?: number; // in minutes
|
||||
@@ -134,8 +134,8 @@ export async function GET(request: NextRequest) {
|
||||
)
|
||||
.all(userId) as Array<{
|
||||
id: string;
|
||||
checkInTime: string;
|
||||
checkOutTime: string | null;
|
||||
checkInTime: string | number; // Can be ISO string or Unix timestamp
|
||||
checkOutTime: string | number | null;
|
||||
type: string;
|
||||
}>;
|
||||
|
||||
@@ -144,13 +144,33 @@ export async function GET(request: NextRequest) {
|
||||
// Get recent check-ins (last 10)
|
||||
const recentCheckIns = attendanceRecords.slice(0, 10).map((record) => {
|
||||
let duration: number | undefined;
|
||||
|
||||
// Convert Unix timestamps to milliseconds for calculation
|
||||
const checkInMs =
|
||||
typeof record.checkInTime === "number"
|
||||
? record.checkInTime * 1000
|
||||
: new Date(record.checkInTime).getTime();
|
||||
|
||||
if (record.checkOutTime) {
|
||||
const checkIn = new Date(record.checkInTime).getTime();
|
||||
const checkOut = new Date(record.checkOutTime).getTime();
|
||||
duration = Math.round((checkOut - checkIn) / (1000 * 60)); // minutes
|
||||
const checkOutMs =
|
||||
typeof record.checkOutTime === "number"
|
||||
? record.checkOutTime * 1000
|
||||
: new Date(record.checkOutTime).getTime();
|
||||
duration = Math.round((checkOutMs - checkInMs) / (1000 * 60)); // minutes
|
||||
}
|
||||
|
||||
// Return with ISO string timestamps for consistency
|
||||
return {
|
||||
...record,
|
||||
id: record.id,
|
||||
checkInTime: new Date(checkInMs).toISOString(),
|
||||
checkOutTime: record.checkOutTime
|
||||
? new Date(
|
||||
typeof record.checkOutTime === "number"
|
||||
? record.checkOutTime * 1000
|
||||
: record.checkOutTime,
|
||||
).toISOString()
|
||||
: null,
|
||||
type: record.type,
|
||||
duration,
|
||||
};
|
||||
});
|
||||
@@ -161,15 +181,25 @@ export async function GET(request: NextRequest) {
|
||||
startOfWeek.setDate(now.getDate() - now.getDay()); // Sunday
|
||||
startOfWeek.setHours(0, 0, 0, 0);
|
||||
|
||||
const thisWeekCheckIns = attendanceRecords.filter(
|
||||
(r) => new Date(r.checkInTime) >= startOfWeek,
|
||||
).length;
|
||||
const thisWeekCheckIns = attendanceRecords.filter((r) => {
|
||||
// checkInTime is Unix timestamp in seconds, convert to milliseconds
|
||||
const checkInDate =
|
||||
typeof r.checkInTime === "number"
|
||||
? new Date(r.checkInTime * 1000)
|
||||
: new Date(r.checkInTime);
|
||||
return checkInDate >= startOfWeek;
|
||||
}).length;
|
||||
|
||||
// Calculate this month's check-ins
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const thisMonthCheckIns = attendanceRecords.filter(
|
||||
(r) => new Date(r.checkInTime) >= startOfMonth,
|
||||
).length;
|
||||
const thisMonthCheckIns = attendanceRecords.filter((r) => {
|
||||
// checkInTime is Unix timestamp in seconds, convert to milliseconds
|
||||
const checkInDate =
|
||||
typeof r.checkInTime === "number"
|
||||
? new Date(r.checkInTime * 1000)
|
||||
: new Date(r.checkInTime);
|
||||
return checkInDate >= startOfMonth;
|
||||
}).length;
|
||||
|
||||
// Calculate current streak (consecutive days with check-ins)
|
||||
let currentStreak = 0;
|
||||
@@ -177,14 +207,24 @@ export async function GET(request: NextRequest) {
|
||||
let tempStreak = 0;
|
||||
let lastDate: Date | null = null;
|
||||
|
||||
const sortedRecords = [...attendanceRecords].sort(
|
||||
(a, b) =>
|
||||
new Date(b.checkInTime).getTime() - new Date(a.checkInTime).getTime(),
|
||||
);
|
||||
const sortedRecords = [...attendanceRecords].sort((a, b) => {
|
||||
const dateA =
|
||||
typeof a.checkInTime === "number"
|
||||
? new Date(a.checkInTime * 1000)
|
||||
: new Date(a.checkInTime);
|
||||
const dateB =
|
||||
typeof b.checkInTime === "number"
|
||||
? new Date(b.checkInTime * 1000)
|
||||
: new Date(b.checkInTime);
|
||||
return dateB.getTime() - dateA.getTime();
|
||||
});
|
||||
|
||||
const uniqueDays = new Set<string>();
|
||||
sortedRecords.forEach((record) => {
|
||||
const date = new Date(record.checkInTime);
|
||||
const date =
|
||||
typeof record.checkInTime === "number"
|
||||
? new Date(record.checkInTime * 1000)
|
||||
: new Date(record.checkInTime);
|
||||
const dateStr = date.toISOString().split("T")[0];
|
||||
uniqueDays.add(dateStr);
|
||||
});
|
||||
@@ -257,10 +297,32 @@ export async function GET(request: NextRequest) {
|
||||
weekEnd.setDate(weekStart.getDate() + 7);
|
||||
|
||||
const weekCheckIns = attendanceRecords.filter((r) => {
|
||||
const checkInDate = new Date(r.checkInTime);
|
||||
// checkInTime is Unix timestamp in seconds, convert to milliseconds
|
||||
const checkInDate =
|
||||
typeof r.checkInTime === "number"
|
||||
? new Date(r.checkInTime * 1000)
|
||||
: new Date(r.checkInTime);
|
||||
return checkInDate >= weekStart && checkInDate < weekEnd;
|
||||
}).length;
|
||||
|
||||
log.debug("Weekly calculation", {
|
||||
weekIndex: i,
|
||||
weekStart: weekStart.toISOString(),
|
||||
weekEnd: weekEnd.toISOString(),
|
||||
weekCheckIns,
|
||||
sampleRecord: attendanceRecords[0]
|
||||
? {
|
||||
checkInTime: attendanceRecords[0].checkInTime,
|
||||
converted:
|
||||
typeof attendanceRecords[0].checkInTime === "number"
|
||||
? new Date(
|
||||
attendanceRecords[0].checkInTime * 1000,
|
||||
).toISOString()
|
||||
: attendanceRecords[0].checkInTime,
|
||||
}
|
||||
: null,
|
||||
});
|
||||
|
||||
const weekGoalsCompleted = db
|
||||
.prepare(
|
||||
`SELECT COUNT(*) as count
|
||||
@@ -308,14 +370,42 @@ export async function GET(request: NextRequest) {
|
||||
userId,
|
||||
totalGoals: goalStats.total,
|
||||
totalCheckIns,
|
||||
weeklyTrendLength: weeklyTrend.length,
|
||||
weeklyTrendSample: weeklyTrend[0],
|
||||
});
|
||||
|
||||
// Return statistics in standardized format that matches mobile app expectations
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
statistics: {
|
||||
userId,
|
||||
...statistics,
|
||||
goals: {
|
||||
totalGoals: goalStats.total,
|
||||
activeGoals: goalStats.active,
|
||||
completedGoals: goalStats.completed,
|
||||
averageProgress: goalStats.avgProgress,
|
||||
goalsByType: Object.entries(goalStats.byType).map(
|
||||
([goalType, count]) => ({
|
||||
goalType,
|
||||
count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
attendance: {
|
||||
totalCheckIns: attendanceStats.totalCheckIns,
|
||||
currentStreak: attendanceStats.currentStreak,
|
||||
longestStreak: attendanceStats.longestStreak,
|
||||
checkInsThisWeek: attendanceStats.thisWeek,
|
||||
checkInsThisMonth: attendanceStats.thisMonth,
|
||||
recentCheckIns: attendanceStats.recentCheckIns,
|
||||
},
|
||||
weeklyTrend: weeklyTrend.map((week) => ({
|
||||
weekLabel: week.week,
|
||||
checkIns: week.checkIns,
|
||||
goalsCompleted: week.goalsCompleted,
|
||||
averageProgress: week.avgProgress,
|
||||
})),
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
@@ -323,15 +413,6 @@ export async function GET(request: NextRequest) {
|
||||
requestId: `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { statistics },
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
requestId: `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("Failed to fetch statistics", error);
|
||||
return NextResponse.json(
|
||||
|
||||
Reference in new issue
Block a user