Skip to content

Commit

Permalink
feat: add brand colors; commence weekly calendar implementation (#146)
Browse files Browse the repository at this point in the history
* feat: add brand colors; commence weekly calendar implementation

* refactor: remove comments and use date-fns helpers
  • Loading branch information
domhhv authored Jan 2, 2025
1 parent 0067e62 commit 28629c6
Show file tree
Hide file tree
Showing 14 changed files with 422 additions and 105 deletions.
4 changes: 2 additions & 2 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ const App = () => {
return (
<Providers>
<AppHeader />
<div className="flex h-full flex-1 flex-col items-start bg-slate-50 dark:bg-slate-950">
<main className="flex h-full flex-1 flex-col items-start bg-background">
<Routes>
<Route path="/calendar/month" element={<MonthCalendar />} />
<Route path="/calendar/week" element={<WeekCalendar />} />
<Route path="/habits" element={<HabitsPage />} />
<Route path="/account" element={<AccountPage />} />
<Route path="*" element={<Navigate to="/calendar/month" replace />} />
</Routes>
</div>
</main>
<Snackbars />
</Providers>
);
Expand Down
53 changes: 24 additions & 29 deletions src/components/calendar-month/CalendarCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { useNotesStore, useOccurrencesStore } from '@stores';
import { useUser } from '@supabase/auth-helpers-react';
import clsx from 'clsx';
import { format } from 'date-fns';
import { format, isToday, isFuture } from 'date-fns';
import { AnimatePresence, motion } from 'framer-motion';
import React from 'react';

Expand Down Expand Up @@ -61,16 +61,10 @@ const CalendarCell = ({
const { removeOccurrence, fetchingOccurrences, occurrencesByDate } =
useOccurrencesStore();
const { notes, fetchingNotes } = useNotesStore();
const today = new Date();
const isToday =
today.getDate() === dateNumber &&
today.getMonth() + 1 === monthNumber &&
today.getFullYear() === fullYear;
const screenSize = useScreenSize();
const date = format(
new Date(fullYear, monthNumber - 1, dateNumber),
'yyyy-MM-dd'
);
const cellDate = new Date(fullYear, monthNumber - 1, dateNumber);
const isTodayCell = isToday(cellDate);
const date = format(cellDate, 'yyyy-MM-dd');

const occurrences = isCalendarDay(date) ? occurrencesByDate[date] || [] : [];
const isMobile = screenSize < 768;
Expand Down Expand Up @@ -103,7 +97,6 @@ const CalendarCell = ({

return onAddOccurrence(dateNumber, monthNumber, fullYear);
}, [
isToday,
dateNumber,
fetchingOccurrences,
fullYear,
Expand Down Expand Up @@ -152,21 +145,21 @@ const CalendarCell = ({
};

const cellRootClassName = clsx(
'group/cell flex h-auto flex-1 flex-col gap-2 border-r-2 border-neutral-500 transition-colors last-of-type:border-r-0 hover:bg-neutral-200 dark:border-neutral-400 dark:hover:bg-neutral-800 lg:h-28',
'group/cell flex h-auto flex-1 flex-col gap-2 border-r-2 border-neutral-500 transition-colors last-of-type:border-r-0 hover:bg-neutral-200 dark:border-neutral-400 dark:hover:bg-neutral-800 lg:h-36',
rangeStatus === 'below-range' && 'cursor-w-resize',
rangeStatus === 'above-range' && 'cursor-e-resize',
position === 'top-left' && 'rounded-tl-md',
position === 'top-right' && 'rounded-tr-md',
position === 'bottom-left' && 'rounded-bl-md',
position === 'bottom-right' && 'rounded-br-md',
isToday &&
'bg-neutral-200 hover:bg-neutral-300 dark:bg-neutral-800 dark:hover:bg-neutral-700'
isTodayCell &&
'bg-background-200 hover:bg-background-300 dark:bg-background-800 dark:hover:bg-background-700'
);

const cellHeaderClassName = clsx(
'flex w-full items-center justify-between border-b-1 border-neutral-500 px-1.5 py-0.5 text-sm dark:border-neutral-400 md:text-base',
'flex w-full items-center justify-between border-b-1 border-neutral-500 px-1.5 py-1.5 text-sm dark:border-neutral-400 md:text-base',
rangeStatus !== 'in-range' && 'text-neutral-400 dark:text-neutral-600',
isToday ? 'w-full self-auto md:self-start' : 'w-full'
isTodayCell ? 'w-full self-auto md:self-start' : 'w-full'
);

return (
Expand All @@ -181,24 +174,26 @@ const CalendarCell = ({
<div className="flex items-center justify-between gap-2">
{rangeStatus === 'in-range' && !isMobile && (
<div className="flex items-center gap-1">
<Tooltip content="Log habit" closeDelay={0}>
<Button
className="h-5 min-w-fit px-2 opacity-100 transition-opacity group-hover/cell:opacity-100 md:opacity-0"
radius="sm"
onClick={handleAddOccurrenceClick}
color="primary"
isDisabled={fetchingOccurrences || !user}
>
<CalendarPlus weight="bold" size={14} />
</Button>
</Tooltip>
{!isFuture(cellDate) && (
<Tooltip content="Log habit" closeDelay={0}>
<Button
className="h-6 min-w-fit px-4 opacity-100 transition-opacity group-hover/cell:opacity-100 md:opacity-0"
radius="sm"
onClick={handleAddOccurrenceClick}
color="primary"
isDisabled={fetchingOccurrences || !user}
>
<CalendarPlus weight="bold" size={18} />
</Button>
</Tooltip>
)}
<Tooltip
content={hasNote ? 'Edit note' : 'Add note'}
closeDelay={0}
>
<Button
className={clsx(
'h-5 min-w-fit px-2 opacity-0 transition-opacity group-hover/cell:opacity-100',
'h-6 min-w-fit px-4 opacity-0 transition-opacity group-hover/cell:opacity-100',
hasNote && 'opacity-100'
)}
radius="sm"
Expand All @@ -207,7 +202,7 @@ const CalendarCell = ({
isDisabled={fetchingNotes || !user}
>
{hasNote ? (
<NotePencil weight="bold" size={14} />
<NotePencil weight="bold" size={18} />
) : (
<NoteBlank weight="bold" size={14} />
)}
Expand Down
38 changes: 28 additions & 10 deletions src/components/calendar-month/CalendarGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import clsx from 'clsx';
import { AnimatePresence, motion } from 'framer-motion';
import React from 'react';
import { useCalendarGrid, useLocale } from 'react-aria';
import { useNavigate } from 'react-router-dom';
import { type CalendarState } from 'react-stately';

import type { CellPosition, CellRangeStatus } from './CalendarCell';
Expand All @@ -20,7 +21,6 @@ type CalendarGridProps = {
) => void;
activeMonthLabel: string;
activeYear: number;
onWeekClick: (weekNum: number) => void;
};

const WEEK_DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
Expand All @@ -31,14 +31,26 @@ const CalendarGrid = ({
onAddOccurrence,
activeMonthLabel,
activeYear,
onWeekClick,
}: CalendarGridProps) => {
const navigate = useNavigate();
const { gridProps } = useCalendarGrid({}, state);
const { locale } = useLocale();
const weeksInMonthCount = getWeeksInMonth(state.visibleRange.start, locale);
const weekIndexes = [...new Array(weeksInMonthCount).keys()];
const { month: activeMonth } = state.visibleRange.start;

const handleWeekClick = (startDate: CalendarDate | null) => {
if (!startDate) {
return;
}

navigate('/calendar/week', {
state: {
startDate: new Date(startDate.year, startDate.month - 1, startDate.day),
},
});
};

const getCellPosition = (
weekIndex: number,
dayIndex: number
Expand Down Expand Up @@ -86,23 +98,24 @@ const CalendarGrid = ({
exit={{ opacity: 0 }}
>
{weekIndexes.map((weekIndex) => {
const weekNum = getYearWeekNumberFromMonthWeek(
const { week } = getYearWeekNumberFromMonthWeek(
activeMonthLabel,
activeYear,
weekIndex
);
const dates = state.getDatesInWeek(weekIndex);

return (
<div key={weekIndex} className="group flex items-center gap-4">
<div key={weekIndex} className="group flex items-center gap-2">
<Button
className={clsx(
'h-[110px] basis-[40px]',
'h-[110px] min-w-fit basis-[40px] p-0',
'hidden' // TODO: show the week number button, open weekly view (WIP) on click
)}
variant="ghost"
onClick={() => onWeekClick(weekNum)}
variant="light"
onClick={() => handleWeekClick(dates[0])}
>
{weekNum}
{week}
</Button>
<div
className={clsx(
Expand All @@ -120,10 +133,15 @@ const CalendarGrid = ({

const { month, day, year } = calendarDate;

console.log({ month2: month });

const rangeStatus: CellRangeStatus =
month < activeMonth
(month < activeMonth ||
(month === 12 && activeMonth === 1)) &&
month !== 1
? 'below-range'
: month > activeMonth
: month > activeMonth ||
(month === 1 && activeMonth === 12)
? 'above-range'
: 'in-range';

Expand Down
7 changes: 0 additions & 7 deletions src/components/calendar-month/MonthCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { capitalizeFirstLetter } from '@utils';
import clsx from 'clsx';
import React from 'react';
import { type AriaButtonProps, useCalendar, useLocale } from 'react-aria';
import { useNavigate } from 'react-router-dom';
import { useCalendarState } from 'react-stately';

import CalendarGrid from './CalendarGrid';
Expand Down Expand Up @@ -44,7 +43,6 @@ const MonthCalendar = () => {
onClose: closeOccurrenceDialog,
} = useDisclosure();
const [activeDate, setActiveDate] = React.useState<Date | null>(null);
const navigate = useNavigate();

React.useEffect(() => {
onRangeChange(
Expand Down Expand Up @@ -120,10 +118,6 @@ const MonthCalendar = () => {
setActiveDate(new Date(fullYear, monthIndex - 1, dateNumber, 12));
};

const handleWeekClick = (weekNumber: number) => {
navigate('/calendar/week', { state: { weekNumber } });
};

const transformButtonProps = (
buttonProps: Pick<AriaButtonProps<'button'>, 'isDisabled' | 'aria-label'>
) => ({
Expand Down Expand Up @@ -156,7 +150,6 @@ const MonthCalendar = () => {
state={state}
onAddOccurrence={handleOccurrenceModalOpen}
onAddNote={handleNoteModalOpen}
onWeekClick={handleWeekClick}
/>
</div>

Expand Down
128 changes: 123 additions & 5 deletions src/components/calendar-week/WeekCalendar.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,130 @@
// import { useLocation } from 'react-router-dom';
/* eslint-disable */

import { useOccurrencesStore } from '@stores';
import { addDays, eachDayOfInterval, startOfDay, startOfWeek } from 'date-fns';
import { motion } from 'framer-motion';
import React from 'react';
import { useLocation } from 'react-router-dom';

import OccurrenceChip from '../calendar-month/OccurrenceChip';

const WEEK_DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

const WeekCalendar = () => {
// const { state } = useLocation();
// const { weekNumber } = state;
const { state } = useLocation();
const { occurrences } = useOccurrencesStore();
const [startOfTheWeek, setStartOfTheWeek] = React.useState(() => {
const initialDate = state?.startDate || new Date();

return startOfWeek(startOfDay(initialDate));
});

if (!state) {
return (
<div className="flex h-full w-full max-w-full flex-1 flex-col p-4">
<h1 className="mt-2 text-center text-xl font-bold">Week Calendar</h1>
<p className="mb-4 text-center text-sm italic text-neutral-400 dark:text-neutral-500">
Weekly calendar is only available when navigating from the month
calendar for now
</p>
</div>
);
}

const { startDate }: { startDate: Date } = state;

const days = eachDayOfInterval({
start: startOfTheWeek,
end: addDays(startOfTheWeek, 6),
});

// console.log({ startDate, startOfTheWeek, days });

// const groupOccurrences = (dayIndex: number, hour: number) => {
// const day = dayIndex === 6 ? 0 : dayIndex + 1;
//
// const relatedOccurrences = occurrences.filter((o) => {
// const date = new Date(o.timestamp);
// return (
// date.getFullYear() === year &&
// date.getMonth() === month &&
// date.getDate() === dates[dayIndex]?.day &&
// date.getDay() === day &&
// date.getHours() === hour
// );
// });
//
// if (hour === 0) {
// console.log('relatedOccurrences', relatedOccurrences);
// }
// return Object.entries(Object.groupBy(relatedOccurrences, (o) => o.habitId));
// };

return (
<div className="flex h-full w-full max-w-full flex-1 flex-col px-2">
<h1>Weekly Calendar</h1>
<div className="flex h-full w-full max-w-full flex-1 flex-col p-4">
<h1 className="mt-2 text-center text-xl font-bold">
{/*Week {week} of {year}*/}
Weekly Calendar
</h1>
<p className="mb-4 text-center text-sm italic text-neutral-400 dark:text-neutral-500">
Coming soon
</p>
{/*{[...Array(25)].map((_, hourIndex) => (*/}
{/* <div*/}
{/* key={hourIndex}*/}
{/* className="ml-4 flex border-b border-neutral-300 first-of-type:border-b-0 last-of-type:border-b-0 dark:border-neutral-600"*/}
{/* >*/}
{/* {[...Array(7)].map((_, dayIndex) => {*/}
{/* if (hourIndex === 0) {*/}
{/* return (*/}
{/* <div*/}
{/* key={`${hourIndex}-${dayIndex}`}*/}
{/* className="mb-2 flex flex-1 items-center justify-center text-neutral-600 dark:text-neutral-300"*/}
{/* >*/}
{/* <p className="font-bold">{WEEK_DAYS[dayIndex]}</p>*/}
{/* </div>*/}
{/* );*/}
{/* }*/}

{/* return (*/}
{/* <div*/}
{/* key={`${hourIndex}-${dayIndex}`}*/}
{/* className="relative h-20 flex-1 border-neutral-300 dark:border-neutral-600 [&:not(:last-of-type)]:border-r"*/}
{/* >*/}
{/* {hourIndex > 1 && dayIndex === 0 && (*/}
{/* <p className="absolute -left-6 -top-3 text-neutral-500 dark:text-neutral-400">*/}
{/* {hourIndex - 1}*/}
{/* </p>*/}
{/* )}*/}
{/* <div className="flex h-full flex-1 flex-wrap justify-center gap-2 overflow-x-auto overflow-y-visible p-2 md:justify-start">*/}
{/* {groupOccurrences(dayIndex, hourIndex - 1).map(*/}
{/* ([habitId, habitOccurrences]) => {*/}
{/* if (!habitOccurrences) {*/}
{/* return null;*/}
{/* }*/}

{/* return (*/}
{/* <motion.div*/}
{/* key={habitId}*/}
{/* initial={{ opacity: 0 }}*/}
{/* animate={{ opacity: 1 }}*/}
{/* exit={{ scale: 0 }}*/}
{/* transition={{ duration: 0.5 }}*/}
{/* >*/}
{/* <OccurrenceChip*/}
{/* occurrences={habitOccurrences}*/}
{/* onDelete={() => null}*/}
{/* />*/}
{/* </motion.div>*/}
{/* );*/}
{/* }*/}
{/* )}*/}
{/* </div>*/}
{/* </div>*/}
{/* );*/}
{/* })}*/}
{/* </div>*/}
{/*))}*/}
</div>
);
};
Expand Down
Loading

0 comments on commit 28629c6

Please sign in to comment.