FlowWeb / src /features /profile /hooks /useProfileStats.js
danylokhodus's picture
init
de55c35
Raw
History Blame Contribute Delete
6.59 kB
import { useState, useEffect } from 'react';
import axios from 'axios';
export const useProfileStats = (user) => {
const [stats, setStats] = useState({
goalsMapped: 0,
nodesCreated: 0,
nodesCompleted: 0,
dayStreak: 0,
points: 0,
xp: 0,
maxXp: 1200,
level: 1,
achievements: [],
loading: true
});
const API_URL = import.meta.env.VITE_API_URL;
useEffect(() => {
if (!user) {
setStats(prev => ({ ...prev, loading: false }));
return;
}
const fetchStats = async () => {
try {
setStats(prev => ({ ...prev, loading: true }));
console.log('[ProfileStats] fetchStats starting. API_URL =', API_URL, 'User =', user);
// Fetch projects (goals)
const graphsRes = await axios.get(`${API_URL}/graphs/user/${user.id}`);
const graphs = graphsRes.data;
console.log('[ProfileStats] Graphs fetched successfully:', graphs);
const goalsMapped = graphs.length;
// Fetch nodes for each project
let nodesCreated = 0;
let nodesCompleted = 0;
let hasConditionalNodes = false;
let hasFinalNodes = false;
const nodesPromises = graphs.map(g => axios.get(`${API_URL}/graphs/${g.id}/full`).catch((err) => {
console.warn(`[ProfileStats] Failed to fetch nodes for graph ${g.id}:`, err);
return { data: { nodes: [] } };
}));
const nodesResponses = await Promise.all(nodesPromises);
nodesResponses.forEach(res => {
const nodes = res.data?.nodes || [];
nodesCreated += nodes.length;
nodesCompleted += nodes.filter(n => n.state === 1 || n.State === 1 || n.state === 'Done' || n.State === 'Done').length;
if (nodes.some(n => {
const t = (n.type || n.Type || '').toLowerCase();
return t === 'conditional' || t === 'condition';
})) hasConditionalNodes = true;
if (nodes.some(n => {
const t = (n.type || n.Type || '').toLowerCase();
return t === 'final' || t === 'goal';
})) hasFinalNodes = true;
});
console.log('[ProfileStats] Nodes count fetched:', nodesCreated, 'Completed:', nodesCompleted);
// Fetch habits for streak
let dayStreak = 0;
try {
const habitsRes = await axios.get(`${API_URL}/habits/user/${user.id}`);
const habits = habitsRes.data;
console.log('[ProfileStats] Habits fetched successfully:', habits);
// Calculate overall day streak
const uniqueDates = [...new Set(habits.map(h => new Date(h.checkedDate).toISOString().split('T')[0]))].sort().reverse();
let expectedDate = new Date();
for (const dateStr of uniqueDates) {
const d = new Date(dateStr);
if (d.toISOString().split('T')[0] === expectedDate.toISOString().split('T')[0]) {
dayStreak++;
expectedDate.setDate(expectedDate.getDate() - 1);
} else if (d > expectedDate) {
continue;
} else {
// If the most recent date was yesterday, that's fine, we might still have a streak
if (dayStreak === 0 && d.toISOString().split('T')[0] === new Date(Date.now() - 86400000).toISOString().split('T')[0]) {
dayStreak++;
expectedDate = new Date(Date.now() - 86400000);
expectedDate.setDate(expectedDate.getDate() - 1);
} else {
break;
}
}
}
} catch (e) {
console.error('[ProfileStats] Failed to fetch habits:', e);
}
console.log('[ProfileStats] Day streak calculated:', dayStreak);
// Calculate points, XP, level
const points = (goalsMapped * 50) + (nodesCreated * 10) + (nodesCompleted * 20) + (dayStreak * 20);
const level = Math.floor(points / 1200) + 1;
const xp = points % 1200;
console.log('[ProfileStats] Calculated totals: Points =', points, 'Level =', level, 'XP =', xp);
// Fetch achievements from new API
let achievements = [];
try {
console.log('[ProfileStats] Fetching achievements progress from API...');
const achRes = await axios.get(`${API_URL}/achievements/progress`);
console.log('[ProfileStats] Achievements progress raw response:', achRes.data);
const mapCategoryToColor = (category) => {
switch (category) {
case 'Projects': return 'bg-blue-100';
case 'Tasks': return 'bg-yellow-100';
case 'Habits': return 'bg-orange-100';
case 'System': return 'bg-gray-100';
default: return 'bg-green-100';
}
};
const mapTitleToIcon = (title) => {
if (title.includes('Canvas')) return 'Map';
if (title.includes('Connect') || title.includes('Decision')) return 'ShieldCheck';
if (title.includes('Zap')) return 'Zap';
if (title.includes('Daily')) return 'Flame';
if (title.includes('Clear Desk')) return 'Target';
return 'Trophy';
};
achievements = achRes.data.map(item => ({
icon: item.achievement.iconUrl || mapTitleToIcon(item.achievement.title),
title: item.achievement.title,
desc: item.achievement.description,
unlocked: item.isUnlocked,
color: mapCategoryToColor(item.achievement.category),
currentValue: item.currentValue,
targetValue: item.achievement.targetValue
}));
console.log('[ProfileStats] Achievements mapped successfully:', achievements);
} catch (e) {
console.error('[ProfileStats] Failed to fetch achievements:', e);
// Fallback to empty if API fails
achievements = [];
}
setStats({
goalsMapped,
nodesCreated,
nodesCompleted,
dayStreak,
points: points >= 1000 ? (points/1000).toFixed(1) + 'k' : points.toString(),
xp,
maxXp: 1200,
level,
achievements,
loading: false
});
} catch (err) {
console.error('[ProfileStats] Critical error in fetchStats main loop:', err);
setStats(prev => ({ ...prev, loading: false }));
}
};
fetchStats();
}, [user]);
return stats;
};