Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/components/occurrence/OccurrenceChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useScreenWidth, useKeyboardShortcut } from '@hooks';
import type { Occurrence } from '@models';
import { StorageBuckets } from '@models';
import { getPublicUrl } from '@services';
import { useOccurrenceDrawerActions } from '@stores';
import { useNotesByOccurrenceId, useOccurrenceDrawerActions } from '@stores';

export type OccurrenceChipProps = {
colorOverride?: string;
Expand All @@ -28,6 +28,7 @@ const OccurrenceChip = ({
isHabitNameShown = false,
occurrences,
}: OccurrenceChipProps) => {
const notes = useNotesByOccurrenceId();
const { openOccurrenceDrawer } = useOccurrenceDrawerActions();
const [occurrence] = occurrences;
const { habit, habitId } = occurrence;
Expand Down Expand Up @@ -110,7 +111,7 @@ const OccurrenceChip = ({

if (
occurrences.some((o) => {
return o.note;
return !!notes[o.id];
})
) {
chip = (
Expand Down
41 changes: 17 additions & 24 deletions src/components/occurrence/OccurrenceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
useHabits,
useNoteActions,
useOccurrenceActions,
useNotesByOccurrenceId,
useOccurrenceDrawerState,
useOccurrenceDrawerActions,
} from '@stores';
Expand All @@ -68,10 +69,10 @@ const OccurrenceForm = ({
const { dayToLog, isOpen, occurrenceToEdit } = useOccurrenceDrawerState();
const { user } = useUser();
const habits = useHabits();
const notes = useNotesByOccurrenceId();
const [isSaving, setIsSaving] = React.useState(false);
const { addNote, deleteNote, updateNote } = useNoteActions();
const { addOccurrence, setOccurrenceNote, updateOccurrence } =
useOccurrenceActions();
const { addOccurrence, updateOccurrence } = useOccurrenceActions();
const [note, handleNoteChange, clearNote] = useTextField();
const [repeat, setRepeat] = React.useState(1);
const [selectedHabitId, setSelectedHabitId] = React.useState('');
Expand All @@ -85,6 +86,10 @@ const OccurrenceForm = ({
React.useState<CalendarDateTime | null>(null);
const { isDesktop, isMobile } = useScreenWidth();

const occurrenceNote = React.useMemo(() => {
return notes[occurrenceToEdit?.id || ''];
}, [notes, occurrenceToEdit]);

const computeOccurrenceDateTime = React.useCallback(
(tz = timeZone) => {
const baseNow = now(tz);
Expand Down Expand Up @@ -160,7 +165,7 @@ const OccurrenceForm = ({

if (isOpen && occurrenceToEdit) {
setSelectedHabitId(occurrenceToEdit.habitId.toString());
handleNoteChange(occurrenceToEdit.note?.content || '');
handleNoteChange(occurrenceNote?.content || '');
setTime(existingOccurrenceDateTime);
setHasSpecificTime(occurrenceToEdit.hasSpecificTime);

Expand All @@ -176,6 +181,7 @@ const OccurrenceForm = ({
}
}, [
dayToLog,
occurrenceNote?.content,
occurrenceToEdit,
existingOccurrenceDateTime,
isOpen,
Expand All @@ -198,7 +204,7 @@ const OccurrenceForm = ({
const hasTimeChanged =
time instanceof ZonedDateTime &&
!!time.compare(occurrenceToEdit.occurredAt);
const hasNoteChanged = note !== (occurrenceToEdit.note?.content || '');
const hasNoteChanged = note !== (occurrenceNote?.content || '');
const hasHabitChanged =
selectedHabitId !== occurrenceToEdit.habitId.toString();
const hasSpecificTimeChanged =
Expand All @@ -220,6 +226,7 @@ const OccurrenceForm = ({
}, [
dayToLog,
occurrenceToEdit,
occurrenceNote?.content,
uploadedFiles.length,
note,
selectedHabitId,
Expand Down Expand Up @@ -300,29 +307,20 @@ const OccurrenceForm = ({
});

if (note) {
let newNote;

if (occurrenceToEdit.note) {
newNote = await updateNote(occurrenceToEdit.note.id, {
if (occurrenceNote) {
await updateNote(occurrenceNote.id, {
content: note,
occurrenceId: occurrenceToEdit.id,
});
} else {
newNote = await addNote({
await addNote({
content: note,
occurrenceId: occurrenceToEdit.id,
userId: user.id,
});
}

setOccurrenceNote(occurrenceToEdit, {
content: newNote.content,
id: newNote.id,
});
} else if (occurrenceToEdit.note) {
await deleteNote(occurrenceToEdit.note.id);

setOccurrenceNote(occurrenceToEdit, null);
} else if (occurrenceNote) {
await deleteNote(occurrenceNote.id);
}
};

Expand Down Expand Up @@ -355,16 +353,11 @@ const OccurrenceForm = ({
});

if (note) {
const newNote = await addNote({
await addNote({
content: note,
occurrenceId: newOccurrence.id,
userId: user.id,
});

setOccurrenceNote(newOccurrence, {
content: newNote.content,
id: newNote.id,
});
}
});

Expand Down
16 changes: 11 additions & 5 deletions src/components/occurrence/OccurrenceListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react';
import { useDateFormatter } from 'react-aria';

import type { Occurrence } from '@models';
import { useNotesByOccurrenceId } from '@stores';

import OccurrenceChip from './OccurrenceChip';

Expand All @@ -23,18 +24,23 @@ const OccurrenceListItem = ({
onEdit,
onRemove,
}: OccurrenceListItemProps) => {
const notes = useNotesByOccurrenceId();
const timeFormatter = useDateFormatter({
hour: 'numeric',
minute: 'numeric',
timeZone: getLocalTimeZone(),
});

const occurrenceNote = React.useMemo(() => {
return notes[occurrence.id];
}, [notes, occurrence]);

return (
<li
key={occurrence.id}
className={cn(
'border-neutral-500 py-2 not-last:border-b',
hasChip && occurrence.note && 'pb-1'
hasChip && occurrenceNote && 'pb-1'
)}
>
<div className="flex items-start justify-between gap-4">
Expand All @@ -53,12 +59,12 @@ const OccurrenceListItem = ({
<span
className={cn(
'italic',
!occurrence.note &&
!occurrenceNote &&
occurrence.hasSpecificTime &&
'text-gray-400'
)}
>
{occurrence.note?.content || '(no note)'}
{occurrenceNote?.content || '(no note)'}
</span>
</>
)}
Expand All @@ -79,9 +85,9 @@ const OccurrenceListItem = ({
</span>
)}
</div>
{occurrence.note && (
{occurrenceNote && (
<div className="text-sm whitespace-pre-wrap">
<span className="italic">{occurrence.note.content}</span>
<span className="italic">{occurrenceNote.content}</span>
</div>
)}
</>
Expand Down
7 changes: 6 additions & 1 deletion src/hooks/use-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ const useSession = () => {
'USER_UPDATED',
].includes(event)
) {
setUser(camelcaseKeys(session.user, { deep: true }));
setUser(
camelcaseKeys(
{ ...session.user, fetchedAt: new Date().toISOString() },
{ deep: true }
)
);
}

if (event === 'SIGNED_OUT') {
Expand Down
9 changes: 5 additions & 4 deletions src/models/note.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import type { Tables, TablesInsert, TablesUpdate } from '@db-types';

import type { Habit } from './habit.model';

type NoteOfOccurrence<T extends Partial<Tables<'notes'>> = Tables<'notes'>> =
CamelCasedPropertiesDeep<
Omit<RequireAtLeastOne<T, 'occurrence_id'>, 'period_date' | 'period_kind'>
>;
export type NoteOfOccurrence<
T extends Partial<Tables<'notes'>> = Tables<'notes'>,
> = CamelCasedPropertiesDeep<
Omit<RequireAtLeastOne<T, 'occurrence_id'>, 'period_date' | 'period_kind'>
>;

export type NoteOfPeriod<T extends Partial<Tables<'notes'>> = Tables<'notes'>> =
CamelCasedPropertiesDeep<
Expand Down
3 changes: 0 additions & 3 deletions src/models/occurrence.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type {
TablesUpdate,
CompositeTypes,
} from '@db-types';
import type { Note } from '@models';

import { type Habit } from './habit.model';
import { type Trait } from './trait.model';
Expand All @@ -24,8 +23,6 @@ type HabitWithTrait = OccurrenceHabit & {

export type RawOccurrence = BaseOccurrence & {
habit: HabitWithTrait;
} & {
note: Pick<Note, 'id' | 'content'> | null;
};

export type Occurrence = Omit<
Expand Down
39 changes: 32 additions & 7 deletions src/services/notes.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { CalendarDate } from '@internationalized/date';
import camelcaseKeys from 'camelcase-keys';
import decamelizeKeys from 'decamelize-keys';

import type { Tables } from '@db-types';
import type { Note, NotesUpdate, NotesInsert, NoteWithHabit } from '@models';
import { supabaseClient } from '@utils';

Expand All @@ -19,22 +20,46 @@ export const createNote = async (note: NotesInsert): Promise<Note> => {
return camelcaseKeys(data);
};

export const listPeriodNotes = async ([rangeStart, rangeEnd]: [
export const listNotes = async ([rangeStart, rangeEnd]: [
CalendarDate,
CalendarDate,
]): Promise<Note[]> => {
const { data, error } = await supabaseClient
const startDateString = rangeStart.toString();
const endDateString = rangeEnd.toString();

const periodNotesPromise = supabaseClient
.from('notes')
.select()
.in('period_kind', ['day', 'week', 'month'])
.gte('period_date', rangeStart.toString())
.lte('period_date', rangeEnd.toString());
.gte('period_date', startDateString)
.lte('period_date', endDateString);

if (error) {
throw new Error(error.message);
const occurrenceNotesPromise = supabaseClient
.from('notes')
.select('*, occurrence:occurrences!inner(occurred_at)')
.gte('occurrences.occurred_at', startDateString)
.lte('occurrences.occurred_at', endDateString);

const [periodResult, occurrenceResult] = await Promise.all([
periodNotesPromise,
occurrenceNotesPromise,
]);

if (periodResult.error) {
throw new Error(periodResult.error.message);
}

return camelcaseKeys(data);
if (occurrenceResult.error) {
throw new Error(occurrenceResult.error.message);
}

const notesMap = new Map<string, Tables<'notes'>>();

for (const note of [...periodResult.data, ...occurrenceResult.data]) {
notesMap.set(note.id, note);
}

return camelcaseKeys([...notesMap.values()], { deep: true });
};

export const updateNote = async (
Expand Down
12 changes: 3 additions & 9 deletions src/services/occurrences.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ export const createOccurrence = async (occurrence: OccurrencesInsert) => {
const { data, error } = await supabaseClient
.from('occurrences')
.insert(decamelizeKeys(occurrence))
.select(
'*, habit:habits(name, icon_path, trait:traits(id, name, color)), note:notes(id, content)'
)
.select('*, habit:habits(name, icon_path, trait:traits(id, name, color))')
.single();

if (error) {
Expand All @@ -36,9 +34,7 @@ export const listOccurrences = async ([rangeStart, rangeEnd]: [
]): Promise<RawOccurrence[]> => {
const { data, error } = await supabaseClient
.from('occurrences')
.select(
'*, habit:habits(name, icon_path, trait:traits(id, name, color)), note:notes(id, content)'
)
.select('*, habit:habits(name, icon_path, trait:traits(id, name, color))')
.order('occurred_at')
.gt('occurred_at', rangeStart.toAbsoluteString())
.lt('occurred_at', rangeEnd.toAbsoluteString());
Expand All @@ -58,9 +54,7 @@ export const patchOccurrence = async (
.from('occurrences')
.update(decamelizeKeys(occurrence))
.eq('id', id)
.select(
'*, habit:habits(name, icon_path, trait:traits(id, name, color)), note:notes(id, content)'
)
.select('*, habit:habits(name, icon_path, trait:traits(id, name, color))')
.single();

if (error) {
Expand Down
5 changes: 4 additions & 1 deletion src/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,8 @@ export const getSession = async () => {
return null;
}

return camelcaseKeys(session.user, { deep: true });
return camelcaseKeys(
{ ...session.user, fetchedAt: new Date().toISOString() },
{ deep: true }
);
};
Loading