feat: enhance dashboard with real streak calculation, answered prayers section, and more stats
This commit is contained in:
1 parent
1d9e2a5d38
commit
55076bbc0e
1 file changed
+110
-7
+110
-7
@@ -10,6 +10,7 @@ interface Prayer {
|
|||||||
text: string;
|
text: string;
|
||||||
prayFor?: string;
|
prayFor?: string;
|
||||||
answered?: boolean;
|
answered?: boolean;
|
||||||
|
answerDetails?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HomePageProps {
|
interface HomePageProps {
|
||||||
@@ -26,7 +27,53 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
const totalPrayers = prayers.length;
|
const totalPrayers = prayers.length;
|
||||||
const uniqueDays = new Set(prayers.map(p => p.date)).size;
|
const uniqueDays = new Set(prayers.map(p => p.date)).size;
|
||||||
const answeredPrayers = prayers.filter(p => p.answered).length;
|
const answeredPrayers = prayers.filter(p => p.answered).length;
|
||||||
const streak = Math.floor(Math.random() * 30) + 1; // Calculate actual streak
|
|
||||||
|
// Calculate actual streak - consecutive days with prayers
|
||||||
|
const calculateStreak = () => {
|
||||||
|
if (prayers.length === 0) return 0;
|
||||||
|
|
||||||
|
const prayerDates = Array.from(new Set(prayers.map(p => p.date))).sort().reverse();
|
||||||
|
let streak = 0;
|
||||||
|
let checkDate = new Date(today);
|
||||||
|
|
||||||
|
for (let i = 0; i < 365; i++) {
|
||||||
|
const dateStr = checkDate.toISOString().split('T')[0];
|
||||||
|
if (prayerDates.includes(dateStr)) {
|
||||||
|
streak++;
|
||||||
|
} else if (i === 0) {
|
||||||
|
// If today has no prayer, streak is broken (unless we haven't prayed today yet, which is ok)
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
checkDate.setDate(checkDate.getDate() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return streak;
|
||||||
|
};
|
||||||
|
|
||||||
|
const streak = calculateStreak();
|
||||||
|
|
||||||
|
// Calculate more stats
|
||||||
|
const avgPrayerLength = totalPrayers > 0
|
||||||
|
? Math.round(prayers.reduce((sum, p) => sum + p.text.length, 0) / totalPrayers)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const answeredPercentage = totalPrayers > 0
|
||||||
|
? Math.round((answeredPrayers / totalPrayers) * 100)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// Get most prayed for person/topic
|
||||||
|
const prayerForCounts: Record<string, number> = {};
|
||||||
|
prayers.forEach(p => {
|
||||||
|
if (p.prayFor) {
|
||||||
|
prayerForCounts[p.prayFor] = (prayerForCounts[p.prayFor] || 0) + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const mostPrayedFor = Object.entries(prayerForCounts).sort((a, b) => b[1] - a[1])[0];
|
||||||
|
|
||||||
|
// Recent answered prayers
|
||||||
|
const recentAnswered = prayers.filter(p => p.answered).slice(0, 3);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 px-4 pb-32">
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 px-4 pb-32">
|
||||||
@@ -67,7 +114,7 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats Grid */}
|
{/* Main Stats Grid */}
|
||||||
<div className="grid grid-cols-2 gap-4 mb-8">
|
<div className="grid grid-cols-2 gap-4 mb-8">
|
||||||
{/* Total Prayers */}
|
{/* Total Prayers */}
|
||||||
<div className="bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 border border-slate-700/50 hover:border-slate-600 transition-all">
|
<div className="bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 border border-slate-700/50 hover:border-slate-600 transition-all">
|
||||||
@@ -83,20 +130,39 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
<div className="text-xs text-slate-500 mt-2">Consistent</div>
|
<div className="text-xs text-slate-500 mt-2">Consistent</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Answered */}
|
{/* Answered Prayers */}
|
||||||
<div className="bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 border border-slate-700/50 hover:border-slate-600 transition-all">
|
<div className="bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 border border-slate-700/50 hover:border-slate-600 transition-all">
|
||||||
<div className="text-slate-400 text-xs font-semibold mb-2">✅ Answered</div>
|
<div className="text-slate-400 text-xs font-semibold mb-2">✅ Answered</div>
|
||||||
<div className="text-3xl font-bold text-green-400">{answeredPrayers}</div>
|
<div className="text-3xl font-bold text-green-400">{answeredPrayers}</div>
|
||||||
<div className="text-xs text-slate-500 mt-2">Blessings</div>
|
<div className="text-xs text-slate-500 mt-2">{answeredPercentage}% answered</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Streak */}
|
{/* Streak */}
|
||||||
<div className="bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 border border-slate-700/50 hover:border-slate-600 transition-all">
|
<div className="bg-slate-800/60 backdrop-blur-xl rounded-xl p-4 border border-slate-700/50 hover:border-slate-600 transition-all">
|
||||||
<div className="text-slate-400 text-xs font-semibold mb-2">🔥 Streak</div>
|
<div className="text-slate-400 text-xs font-semibold mb-2">🔥 Streak</div>
|
||||||
<div className="text-3xl font-bold text-orange-400">{streak} days</div>
|
<div className="text-3xl font-bold text-orange-400">{streak}</div>
|
||||||
<div className="text-xs text-slate-500 mt-2">Keep going!</div>
|
<div className="text-xs text-slate-500 mt-2">consecutive days</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Secondary Stats */}
|
||||||
|
<div className="grid grid-cols-2 gap-4 mb-8">
|
||||||
|
{/* Avg Prayer Length */}
|
||||||
|
<div className="bg-purple-900/20 backdrop-blur-xl rounded-xl p-4 border border-purple-500/30">
|
||||||
|
<div className="text-purple-300 text-xs font-semibold mb-2">📊 Avg Length</div>
|
||||||
|
<div className="text-2xl font-bold text-purple-200">{avgPrayerLength}</div>
|
||||||
|
<div className="text-xs text-purple-400 mt-2">characters</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Most Prayed For */}
|
||||||
|
{mostPrayedFor && (
|
||||||
|
<div className="bg-pink-900/20 backdrop-blur-xl rounded-xl p-4 border border-pink-500/30">
|
||||||
|
<div className="text-pink-300 text-xs font-semibold mb-2">💝 Most Prayed For</div>
|
||||||
|
<div className="text-sm font-bold text-pink-200 line-clamp-1">{mostPrayedFor[0]}</div>
|
||||||
|
<div className="text-xs text-pink-400 mt-2">{mostPrayedFor[1]} times</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CTA Button */}
|
{/* CTA Button */}
|
||||||
@@ -112,7 +178,7 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
|
|
||||||
{/* Recent Prayers */}
|
{/* Recent Prayers */}
|
||||||
{recentPrayers.length > 0 && (
|
{recentPrayers.length > 0 && (
|
||||||
<div className="max-w-md mx-auto">
|
<div className="max-w-md mx-auto mb-12">
|
||||||
<h2 className="text-lg font-bold text-white mb-4 flex items-center gap-2">
|
<h2 className="text-lg font-bold text-white mb-4 flex items-center gap-2">
|
||||||
<span>📖</span> Recent Prayers
|
<span>📖</span> Recent Prayers
|
||||||
</h2>
|
</h2>
|
||||||
@@ -144,6 +210,43 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Recent Answered Prayers */}
|
||||||
|
{recentAnswered.length > 0 && (
|
||||||
|
<div className="max-w-md mx-auto mb-12">
|
||||||
|
<h2 className="text-lg font-bold text-green-400 mb-4 flex items-center gap-2">
|
||||||
|
<span>✨</span> Recently Answered
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{recentAnswered.map((prayer) => (
|
||||||
|
<div
|
||||||
|
key={prayer.id}
|
||||||
|
className="bg-green-900/20 backdrop-blur-xl rounded-xl p-4 border border-green-500/30 hover:border-green-400/50 transition-all"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="text-2xl">✅</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
{prayer.prayFor && (
|
||||||
|
<div className="text-xs font-bold text-green-300 mb-1 uppercase tracking-wider">
|
||||||
|
🙏 {prayer.prayFor}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p className="text-sm text-slate-300 line-clamp-2">{prayer.text}</p>
|
||||||
|
{prayer.answerDetails && (
|
||||||
|
<p className="text-xs text-green-400 mt-2 italic">
|
||||||
|
💬 "{prayer.answerDetails.substring(0, 60)}..."
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="text-xs text-slate-500 mt-2">
|
||||||
|
{new Date(prayer.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Empty State */}
|
{/* Empty State */}
|
||||||
{prayers.length === 0 && (
|
{prayers.length === 0 && (
|
||||||
<div className="max-w-md mx-auto text-center py-12">
|
<div className="max-w-md mx-auto text-center py-12">
|
||||||
|
|||||||
Reference in new issue
Block a user