mirror of
https://github.com/CarGDev/pomodoro.git
synced 2025-09-18 17:18:28 +00:00

Initializes the project with necessary configurations, including Replit settings and a .gitignore file. It also introduces foundational UI components for the application, such as buttons, dialogs, and layout elements, likely for the Pomodoro timer application. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 59a5ae27-3c71-459b-b42f-fe14121bf9c3 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3007b6f6-d03b-45e1-9ed1-7ce8de18ea24/59a5ae27-3c71-459b-b42f-fe14121bf9c3/Uupe4F4
172 lines
5.9 KiB
TypeScript
172 lines
5.9 KiB
TypeScript
import type { Express } from "express";
|
||
import { createServer, type Server } from "http";
|
||
import { storage } from "./storage";
|
||
import { insertSessionSchema } from "@shared/schema";
|
||
import { z } from "zod";
|
||
|
||
export async function registerRoutes(app: Express): Promise<Server> {
|
||
// Create session
|
||
app.post("/api/sessions", async (req, res) => {
|
||
try {
|
||
const sessionData = insertSessionSchema.parse(req.body);
|
||
const session = await storage.createSession(sessionData);
|
||
|
||
// Update device activity
|
||
await storage.updateDeviceProfileActivity(sessionData.deviceId);
|
||
|
||
res.status(201).json({ ok: true, id: session.id });
|
||
} catch (error) {
|
||
if (error instanceof z.ZodError) {
|
||
return res.status(400).json({ message: "Invalid session data", errors: error.errors });
|
||
}
|
||
res.status(500).json({ message: "Failed to create session" });
|
||
}
|
||
});
|
||
|
||
// Get suggestions for device
|
||
app.get("/api/suggestions", async (req, res) => {
|
||
try {
|
||
const deviceId = req.query.deviceId as string;
|
||
if (!deviceId) {
|
||
return res.status(400).json({ message: "Device ID is required" });
|
||
}
|
||
|
||
// Get recent sessions (last 7 days)
|
||
const sevenDaysAgo = new Date();
|
||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||
const recentSessions = await storage.getSessionsInDateRange(deviceId, sevenDaysAgo, new Date());
|
||
|
||
const suggestions = generateSuggestions(recentSessions);
|
||
res.json({ suggestions });
|
||
} catch (error) {
|
||
res.status(500).json({ message: "Failed to get suggestions" });
|
||
}
|
||
});
|
||
|
||
// Get stats summary
|
||
app.get("/api/stats/summary", async (req, res) => {
|
||
try {
|
||
const deviceId = req.query.deviceId as string;
|
||
if (!deviceId) {
|
||
return res.status(400).json({ message: "Device ID is required" });
|
||
}
|
||
|
||
const sevenDaysAgo = new Date();
|
||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||
const thirtyDaysAgo = new Date();
|
||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||
|
||
const last7dSessions = await storage.getSessionsInDateRange(deviceId, sevenDaysAgo, new Date());
|
||
const last30dSessions = await storage.getSessionsInDateRange(deviceId, thirtyDaysAgo, new Date());
|
||
|
||
const last7d = calculateStats(last7dSessions.filter(s => s.type === 'focus'));
|
||
const last30d = calculateStats(last30dSessions.filter(s => s.type === 'focus'));
|
||
|
||
res.json({ last7d, last30d });
|
||
} catch (error) {
|
||
res.status(500).json({ message: "Failed to get stats" });
|
||
}
|
||
});
|
||
|
||
const httpServer = createServer(app);
|
||
return httpServer;
|
||
}
|
||
|
||
function generateSuggestions(sessions: any[]) {
|
||
const focusSessions = sessions.filter(s => s.type === 'focus');
|
||
|
||
if (focusSessions.length === 0) {
|
||
return [
|
||
{ minutes: 25, reason: "Classic Pomodoro technique - great for starting out." },
|
||
{ minutes: 15, reason: "Short sprints help build focus habits." },
|
||
{ minutes: 40, reason: "Extended sessions for deep work." }
|
||
];
|
||
}
|
||
|
||
const completedSessions = focusSessions.filter(s => s.completed);
|
||
const completionRate = completedSessions.length / focusSessions.length;
|
||
|
||
// Analyze successful durations
|
||
const successfulDurations = completedSessions.map(s => s.intendedMinutes);
|
||
const avgSuccessfulDuration = successfulDurations.reduce((a, b) => a + b, 0) / successfulDurations.length;
|
||
|
||
// Analyze time of day patterns
|
||
const now = new Date();
|
||
const currentHour = now.getHours();
|
||
const timeOfDaySuccess = getTimeOfDaySuccess(completedSessions, currentHour);
|
||
|
||
const suggestions = [];
|
||
|
||
// Primary suggestion based on completion rate
|
||
if (completionRate >= 0.8) {
|
||
suggestions.push({
|
||
minutes: Math.round(avgSuccessfulDuration),
|
||
reason: `Your 7-day completion rate is ${Math.round(completionRate * 100)}% at ${Math.round(avgSuccessfulDuration)}m.`
|
||
});
|
||
} else {
|
||
suggestions.push({
|
||
minutes: 25,
|
||
reason: "Classic 25min sessions have proven effective for most users."
|
||
});
|
||
}
|
||
|
||
// Time-based suggestion
|
||
if (timeOfDaySuccess.avgDuration > 0) {
|
||
suggestions.push({
|
||
minutes: Math.round(timeOfDaySuccess.avgDuration),
|
||
reason: `${getTimeOfDayLabel(currentHour)}: best focus avg ${Math.round(timeOfDaySuccess.avgDuration - 3)}–${Math.round(timeOfDaySuccess.avgDuration + 3)}m.`
|
||
});
|
||
}
|
||
|
||
// Interruption-based suggestion
|
||
const avgInterruptions = focusSessions.reduce((sum, s) => sum + s.interruptions, 0) / focusSessions.length;
|
||
if (avgInterruptions > 1) {
|
||
suggestions.push({
|
||
minutes: 15,
|
||
reason: "High interruption periods benefit from short sprints."
|
||
});
|
||
} else {
|
||
suggestions.push({
|
||
minutes: 40,
|
||
reason: "Low interruption environment - try longer deep work sessions."
|
||
});
|
||
}
|
||
|
||
return suggestions.slice(0, 3);
|
||
}
|
||
|
||
function calculateStats(focusSessions: any[]) {
|
||
const completed = focusSessions.filter(s => s.completed);
|
||
const completionRate = focusSessions.length > 0 ? completed.length / focusSessions.length : 0;
|
||
const avgFocusMin = completed.length > 0
|
||
? completed.reduce((sum, s) => sum + s.intendedMinutes, 0) / completed.length
|
||
: 0;
|
||
|
||
return {
|
||
sessions: focusSessions.length,
|
||
completionRate: Math.round(completionRate * 100) / 100,
|
||
avgFocusMin: Math.round(avgFocusMin)
|
||
};
|
||
}
|
||
|
||
function getTimeOfDaySuccess(sessions: any[], currentHour: number) {
|
||
const timeRangeSessions = sessions.filter(s => {
|
||
const sessionHour = new Date(s.startedAt).getHours();
|
||
return Math.abs(sessionHour - currentHour) <= 2;
|
||
});
|
||
|
||
if (timeRangeSessions.length === 0) {
|
||
return { avgDuration: 0 };
|
||
}
|
||
|
||
const avgDuration = timeRangeSessions.reduce((sum, s) => sum + s.intendedMinutes, 0) / timeRangeSessions.length;
|
||
return { avgDuration };
|
||
}
|
||
|
||
function getTimeOfDayLabel(hour: number) {
|
||
if (hour >= 6 && hour < 12) return "Mornings";
|
||
if (hour >= 12 && hour < 17) return "Afternoons";
|
||
if (hour >= 17 && hour < 21) return "Evenings";
|
||
return "Late hours";
|
||
}
|