Skip to content

Commit

Permalink
feat: allow adding notes to days; improve calendar ui; fix get_longes…
Browse files Browse the repository at this point in the history
…t_streak fn (#142)
  • Loading branch information
domhhv authored Dec 29, 2024
1 parent 471ee60 commit 1a3e067
Show file tree
Hide file tree
Showing 30 changed files with 646 additions and 308 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"no-console": ["warn", { "allow": ["warn", "error"] }],
"object-shorthand": "error",
"import/no-duplicates": "error",
"react/react-in-jsx-scope": "off",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-unused-vars": [
"error",
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,12 @@ To set up a local Supabase instance, run the following commands (Docker required

There are a few ways to create and run migrations in the project.

- Diffing the database schema:
- Diffing the database schema to automatically generate a new migration file:

_Do the necessary changes in the local Supabase studio and then run the following to automatically generate a new migration file:_

```bash
yarn db:diff
yarn db:diff -f <your-migration-name>
```

- Creating a new migration file manually:
Expand All @@ -179,7 +179,7 @@ _To create a new migration file manually, run the following command:_
yarn db:migration:new <your-migration-name>
```

Either way, the new migration file will be created in the `supabase/migrations` directory. You can then apply the migration by running:
Either way, the new migration file will be created in the `supabase/migrations` directory. Write/change the SQL queries in the migration file to reflect the changes you want to make to the database schema. Then, apply the migration by running:

```bash
yarn db:migration:up
Expand Down
28 changes: 23 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,29 @@
justify-content: center;
align-items: center;
height: 100%;

span {
font-size: 1.2rem;
}

img {
width: 24px;
height: 24px;
margin-right: 8px;
}
}

.loader-logo {
animation: spin 2s linear infinite;
}

.loader-container img {
margin-right: 8px;
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
Expand All @@ -55,10 +74,9 @@
<img
src="/android-chrome-192x192.png"
alt="Habilify logo"
width="16"
height="16"
class="loader-logo"
/>
We're preparing the app...
<span>We're preparing the app...</span>
</div>
</div>
</body>
Expand Down
7 changes: 0 additions & 7 deletions src/components/calendar/Calendar.test.tsx

This file was deleted.

61 changes: 45 additions & 16 deletions src/components/calendar/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import React from 'react';
import { type AriaButtonProps, useCalendar, useLocale } from 'react-aria';
import { useCalendarState } from 'react-stately';

import AddOccurrenceDialog from './AddOccurrenceDialog';
import CalendarGrid from './CalendarGrid';
import CalendarHeader from './CalendarHeader';
import NoteDialog from './NoteDialog';
import OccurrenceDialog from './OccurrenceDialog';

const createCalendar = (identifier: string) => {
switch (identifier) {
Expand All @@ -32,9 +33,14 @@ const Calendar = () => {
const { calendarProps, prevButtonProps, nextButtonProps, title } =
useCalendar({}, state);
const {
isOpen: isAddOccurrenceDialogOpen,
onOpen: openAddOccurrenceDialog,
onClose: closeDayOccurrenceDialog,
isOpen: isNoteDialogOpen,
onOpen: openNoteDialog,
onClose: closeNoteDialog,
} = useDisclosure();
const {
isOpen: isOccurrenceDialogOpen,
onOpen: openOccurrenceDialog,
onClose: closeOccurrenceDialog,
} = useDisclosure();
const [activeDate, setActiveDate] = React.useState<Date | null>(null);

Expand Down Expand Up @@ -87,26 +93,42 @@ const Calendar = () => {
setFocusedDate(year, month + 1, day);
};

const handleDayModalDialogOpen = (
const calendarContainerClassName = clsx(
'flex h-full w-full max-w-full flex-1 flex-col gap-2 p-0 px-2 pb-8 lg:gap-4 lg:px-0 lg:px-16 lg:py-4',
isOccurrenceDialogOpen && 'pointer-events-none'
);

const handleOccurrenceModalClose = () => {
window.setTimeout(() => {
closeOccurrenceDialog();
setActiveDate(null);
}, 0);
};

const handleOccurrenceModalOpen = (
dateNumber: number,
monthIndex: number,
fullYear: number
) => {
openAddOccurrenceDialog();
openOccurrenceDialog();
setActiveDate(new Date(fullYear, monthIndex - 1, dateNumber, 12));
};

const handleDayModalDialogClose = () => {
const handleNoteModalClose = () => {
window.setTimeout(() => {
closeDayOccurrenceDialog();
closeNoteDialog();
setActiveDate(null);
}, 0);
};

const calendarContainerClassName = clsx(
'flex h-full w-full max-w-full flex-1 flex-col gap-2 p-0 pb-8 lg:gap-4 lg:px-16 lg:py-4',
isAddOccurrenceDialogOpen && 'pointer-events-none'
);
const handleNoteModalOpen = (
dateNumber: number,
monthIndex: number,
fullYear: number
) => {
openNoteDialog();
setActiveDate(new Date(fullYear, monthIndex - 1, dateNumber, 12));
};

return (
<>
Expand All @@ -126,13 +148,20 @@ const Calendar = () => {
activeMonthLabel={capitalizeFirstLetter(activeMonthLabel)}
activeYear={Number(activeYear)}
state={state}
onDayModalDialogOpen={handleDayModalDialogOpen}
onAddOccurrence={handleOccurrenceModalOpen}
onAddNote={handleNoteModalOpen}
/>
</div>

<AddOccurrenceDialog
isOpen={isAddOccurrenceDialogOpen}
onClose={handleDayModalDialogClose}
<OccurrenceDialog
isOpen={isOccurrenceDialogOpen}
onClose={handleOccurrenceModalClose}
date={activeDate}
/>

<NoteDialog
open={isNoteDialogOpen}
onClose={handleNoteModalClose}
date={activeDate}
/>
</>
Expand Down
7 changes: 0 additions & 7 deletions src/components/calendar/CalendarCell.test.tsx

This file was deleted.

98 changes: 79 additions & 19 deletions src/components/calendar/CalendarCell.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { useScreenSize } from '@hooks';
import { CalendarBlank } from '@phosphor-icons/react';
import { useOccurrencesStore } from '@stores';
import { Button, Tooltip } from '@nextui-org/react';
import {
CalendarBlank,
CalendarPlus,
NoteBlank,
NotePencil,
} from '@phosphor-icons/react';
import { useNotesStore, useOccurrencesStore } from '@stores';
import { useUser } from '@supabase/auth-helpers-react';
import clsx from 'clsx';
import { format } from 'date-fns';
Expand All @@ -22,7 +28,16 @@ type CalendarCellProps = {
dateNumber: number;
monthNumber: number;
fullYear: number;
onClick: (dateNumber: number, monthNumber: number, fullYear: number) => void;
onAddNote: (
dateNumber: number,
monthNumber: number,
fullYear: number
) => void;
onAddOccurrence: (
dateNumber: number,
monthNumber: number,
fullYear: number
) => void;
onNavigateBack?: () => void;
onNavigateForward?: () => void;
rangeStatus: CellRangeStatus;
Expand All @@ -35,14 +50,16 @@ const CalendarCell = ({
fullYear,
onNavigateBack,
onNavigateForward,
onClick,
onAddNote,
onAddOccurrence,
rangeStatus,
position,
}: CalendarCellProps) => {
const cellRef = React.useRef<HTMLDivElement>(null);
const user = useUser();
const { removeOccurrence, fetchingOccurrences, occurrencesByDate } =
useOccurrencesStore();
const { notes, fetchingNotes } = useNotesStore();
const today = new Date();
const isToday =
today.getDate() === dateNumber &&
Expand All @@ -54,10 +71,20 @@ const CalendarCell = ({
'yyyy-MM-dd'
);
const occurrences = occurrencesByDate[date] || [];
const isMobile = screenSize < 768;
const hasNote = notes.some((note) => note.day === date);

const groupedOccurrences = Object.groupBy(occurrences, (o) => o.habitId);

const handleClick = React.useCallback(() => {
const handleAddNoteClick = React.useCallback(() => {
if (fetchingOccurrences || !user) {
return null;
}

return onAddNote(dateNumber, monthNumber, fullYear);
}, [fetchingOccurrences, user, dateNumber, monthNumber, fullYear, onAddNote]);

const handleAddOccurrenceClick = React.useCallback(() => {
if (fetchingOccurrences || !user) {
return null;
}
Expand All @@ -72,14 +99,14 @@ const CalendarCell = ({
}
}

return onClick(dateNumber, monthNumber, fullYear);
return onAddOccurrence(dateNumber, monthNumber, fullYear);
}, [
isToday,
dateNumber,
fetchingOccurrences,
fullYear,
monthNumber,
onClick,
onAddOccurrence,
onNavigateBack,
onNavigateForward,
rangeStatus,
Expand All @@ -95,7 +122,7 @@ const CalendarCell = ({

const enterHandler = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
handleClick();
handleAddOccurrenceClick();
}
};

Expand All @@ -104,7 +131,7 @@ const CalendarCell = ({
return () => {
cell.removeEventListener('keydown', enterHandler);
};
}, [cellRef, handleClick]);
}, [cellRef, handleAddOccurrenceClick]);

const handleOccurrenceDelete = async (
occurrenceId: number,
Expand All @@ -119,17 +146,15 @@ const CalendarCell = ({
return null;
}

const isMobile = screenSize < 768;

if (isMobile) {
return <CalendarBlank weight="bold" size={18} />;
return <CalendarBlank size={14} weight="bold" />;
}

return <p className="font-bold">Today</p>;
};

const cellRootClassName = clsx(
'flex h-auto flex-1 flex-col border-r-2 border-neutral-500 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-28',
rangeStatus === 'below-range' && 'cursor-w-resize',
rangeStatus === 'above-range' && 'cursor-e-resize',
position === 'top-left' && 'rounded-tl-md',
Expand All @@ -141,23 +166,58 @@ const CalendarCell = ({
);

const cellHeaderClassName = clsx(
'flex items-center justify-between rounded-t px-1.5 py-0.5',
rangeStatus !== 'in-range' && 'text-neutral-400 dark:text-neutral-600'
'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',
rangeStatus !== 'in-range' && 'text-neutral-400 dark:text-neutral-600',
isToday ? 'w-full self-auto md:self-start' : 'w-full'
);

return (
<div
className={cellRootClassName}
ref={cellRef}
onClick={handleClick}
tabIndex={0}
role="button"
onClick={isMobile ? handleAddOccurrenceClick : undefined}
>
<div className={cellHeaderClassName}>
<p className="font-bold">{dateNumber}</p>
{renderToday()}
<div className="flex items-center justify-between gap-2">
{rangeStatus === 'in-range' && !isMobile && (
<div className="flex items-center gap-1 opacity-100 transition-opacity group-hover/cell:opacity-100 md:opacity-0">
<Tooltip content="Log habit" closeDelay={0}>
<Button
className="h-5 min-w-fit px-2"
radius="sm"
onClick={handleAddOccurrenceClick}
color="primary"
isDisabled={fetchingOccurrences || !user}
>
<CalendarPlus weight="bold" size={14} />
</Button>
</Tooltip>
<Tooltip
content={hasNote ? 'Edit note' : 'Add note'}
closeDelay={0}
>
<Button
className="h-5 min-w-fit px-2"
radius="sm"
onClick={handleAddNoteClick}
color="primary"
isDisabled={fetchingNotes || !user}
>
{hasNote ? (
<NotePencil weight="bold" size={14} />
) : (
<NoteBlank weight="bold" size={14} />
)}
</Button>
</Tooltip>
</div>
)}
{renderToday()}
</div>
</div>
<div className="flex flex-wrap gap-1 overflow-auto px-2 py-0.5 pb-2">
<div className="flex flex-wrap justify-center gap-1 overflow-x-auto overflow-y-visible px-0 py-0.5 pb-2 md:justify-start md:px-2">
<AnimatePresence mode="sync">
{Object.entries(groupedOccurrences).map(
([habitId, habitOccurrences]) => {
Expand Down
7 changes: 0 additions & 7 deletions src/components/calendar/CalendarGrid.test.tsx

This file was deleted.

Loading

0 comments on commit 1a3e067

Please sign in to comment.