adding today feature on main habit page, show streaks feature on settings page and improved dark mode functionality

This commit is contained in:
Harivansh Rathi 2024-11-20 14:44:45 -05:00
parent 1bb06006e8
commit 6b6cba21e5
9 changed files with 705 additions and 362 deletions

View file

@ -1,5 +1,6 @@
import React from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { useThemeContext } from '../contexts/ThemeContext';
import { Habit } from '../types';
interface CalendarProps {
@ -10,46 +11,55 @@ interface CalendarProps {
getCompletedHabitsForDate: (date: string) => Habit[];
}
export function Calendar({
export const Calendar: React.FC<CalendarProps> = ({
currentMonth,
habits,
onChangeMonth,
getDaysInMonth,
getCompletedHabitsForDate
}: CalendarProps) {
}) => {
const { theme } = useThemeContext();
const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const getFirstDayOfMonth = (year: number, month: number) => {
const date = new Date(year, month, 1);
// Convert Sunday (0) to 6 for our Monday-based week
return date.getDay() === 0 ? 6 : date.getDay() - 1;
};
// Helper function to format date to YYYY-MM-DD
const formatDate = (date: Date): string => {
return date.toISOString().split('T')[0];
};
// Get today's date in YYYY-MM-DD format
const today = formatDate(new Date());
return (
<div className="bg-white dark:bg-gray-900 rounded-lg shadow-lg p-6">
<div className="flex items-center justify-between mb-8">
<h2 className="text-2xl font-bold dark:text-white">
<div className={`rounded-lg shadow-md p-6 ${theme.calendar.background}`}>
<div className="flex justify-between items-center mb-8">
<h2 className={`text-2xl font-bold ${theme.calendar.header}`}>
{currentMonth.toLocaleString('default', { month: 'long', year: 'numeric' })}
</h2>
<div className="flex space-x-2">
<div className="flex space-x-3">
<button
onClick={() => onChangeMonth('prev')}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full transition-colors duration-200"
className={`p-2.5 rounded-lg ${theme.calendar.navigation.button}`}
>
<ChevronLeft className="h-5 w-5 dark:text-white" />
<ChevronLeft className={theme.calendar.navigation.icon} />
</button>
<button
onClick={() => onChangeMonth('next')}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full transition-colors duration-200"
className={`p-2.5 rounded-lg ${theme.calendar.navigation.button}`}
>
<ChevronRight className="h-5 w-5 dark:text-white" />
<ChevronRight className={theme.calendar.navigation.icon} />
</button>
</div>
</div>
<div className="grid grid-cols-7 gap-4">
{daysOfWeek.map(day => (
<div key={day} className="text-center font-semibold text-gray-600 dark:text-gray-300 mb-2">
<div key={day} className={`text-center font-semibold mb-2 ${theme.calendar.weekDay}`}>
{day}
</div>
))}
@ -61,13 +71,12 @@ export function Calendar({
const daysInMonth = getDaysInMonth(year, month);
const daysInPrevMonth = getDaysInMonth(year, month - 1);
// Calculate days to show
const days = [];
// Previous month days
for (let i = 0; i < firstDayOfMonth; i++) {
const day = daysInPrevMonth - firstDayOfMonth + i + 1;
const date = new Date(year, month - 1, day).toISOString().split('T')[0];
const date = formatDate(new Date(year, month - 1, day));
days.push({
date,
dayNumber: day,
@ -77,7 +86,7 @@ export function Calendar({
// Current month days
for (let i = 1; i <= daysInMonth; i++) {
const date = new Date(year, month, i).toISOString().split('T')[0];
const date = formatDate(new Date(year, month, i));
days.push({
date,
dayNumber: i,
@ -85,10 +94,10 @@ export function Calendar({
});
}
// Next month days to complete the grid
const remainingDays = 42 - days.length; // 6 rows * 7 days
// Next month days
const remainingDays = 42 - days.length;
for (let i = 1; i <= remainingDays; i++) {
const date = new Date(year, month + 1, i).toISOString().split('T')[0];
const date = formatDate(new Date(year, month + 1, i));
days.push({
date,
dayNumber: i,
@ -99,41 +108,62 @@ export function Calendar({
return days.map(({ date, dayNumber, isCurrentMonth }) => {
const completedHabits = getCompletedHabitsForDate(date);
const incompleteHabits = habits.filter(habit => !habit.completedDates.includes(date));
const isToday = date === today;
return (
<div
key={date}
className={`border dark:border-gray-700 rounded-lg p-3 min-h-[80px] relative group hover:shadow-md transition-shadow duration-200 ${
!isCurrentMonth ? 'bg-gray-50 dark:bg-gray-800/50' : ''
}`}
className={`
border rounded-lg p-3 min-h-[80px] relative
${theme.border}
${isCurrentMonth ? theme.calendar.day.default : theme.calendar.day.otherMonth}
${isToday ? `border-2 ${theme.calendar.day.today}` : ''}
`}
>
<span className={`text-sm font-medium ${
isCurrentMonth
? 'dark:text-white'
: 'text-gray-400 dark:text-gray-500'
}`}>
<span className={`font-medium ${isCurrentMonth ? theme.text : theme.calendar.day.otherMonth}`}>
{dayNumber}
</span>
{habits.length > 0 && (
<div className="absolute bottom-3 left-1/2 transform -translate-x-1/2">
<div className="relative">
<div className="group relative inline-block">
<div
className={`h-3 w-3 ${
completedHabits.length > 0
? 'bg-green-500 shadow-sm shadow-green-200'
: 'bg-gray-300 dark:bg-gray-600'
} rounded-full transition-colors duration-200`}
className={`
h-4 w-4 rounded-full cursor-pointer
transition-colors duration-200
${completedHabits.length > 0
? 'bg-[#2ecc71] dark:bg-[#2ecc71] shadow-sm shadow-[#2ecc7150]'
: `bg-[#e9e9e8] dark:bg-[#393939]`
}
`}
/>
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-3 hidden group-hover:block">
<div className="bg-white dark:bg-gray-800 text-sm rounded-lg shadow-lg p-4 border dark:border-gray-700 min-w-[200px]">
<div
className={`
absolute bottom-full left-1/2 -translate-x-1/2 mb-2
opacity-0 invisible
group-hover:opacity-100 group-hover:visible
transition-all duration-150 ease-in-out
z-50 transform
translate-y-1 group-hover:translate-y-0
`}
>
<div className={`
rounded-lg p-4
min-w-[200px] max-w-[300px]
${theme.calendar.tooltip.background}
${theme.calendar.tooltip.border}
${theme.calendar.tooltip.shadow}
border
backdrop-blur-sm
pointer-events-none
`}>
{completedHabits.length > 0 && (
<div className="mb-3">
<span className="text-green-500 font-semibold block mb-1">
<span className="text-[#2ecc71] font-semibold block mb-2">
Completed
</span>
<ul className="space-y-1">
<ul className="space-y-1.5">
{completedHabits.map(habit => (
<li key={habit.id} className="text-gray-600 dark:text-gray-300">
<li key={habit.id} className={`${theme.text} text-sm truncate`}>
{habit.name}
</li>
))}
@ -142,12 +172,12 @@ export function Calendar({
)}
{incompleteHabits.length > 0 && (
<div>
<span className="text-red-500 font-semibold block mb-1">
<span className="text-[#e74c3c] font-semibold block mb-2">
Pending
</span>
<ul className="space-y-1">
<ul className="space-y-1.5">
{incompleteHabits.map(habit => (
<li key={habit.id} className="text-gray-600 dark:text-gray-300">
<li key={habit.id} className={`${theme.text} text-sm truncate`}>
{habit.name}
</li>
))}
@ -166,4 +196,4 @@ export function Calendar({
</div>
</div>
);
}
};