Skip to content

Commit

Permalink
fix(habits): get_longest_streak db fn returns correct data (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
domhhv authored Jan 26, 2025
1 parent 55d2c3b commit 26dfe80
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 39 deletions.
4 changes: 4 additions & 0 deletions src/components/calendar-month/NoteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ const NoteDialog = ({ open, onClose, date }: NoteDialogProps) => {
if (existingNote?.content && !content) {
setContent(existingNote.content);
}

return () => {
setContent('');
};
}, [existingNote, content]);

const handleSubmit: MouseEventHandler<HTMLButtonElement> = async (event) => {
Expand Down
23 changes: 18 additions & 5 deletions src/components/habit/habits-page/HabitLastEntryCell.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { Tooltip } from '@nextui-org/react';
import { getLatestHabitOccurrenceTimestamp } from '@services';
import { capitalizeFirstLetter } from '@utils';
import { formatDistanceStrict, formatRelative, isThisWeek } from 'date-fns';
import {
format,
formatDistanceStrict,
formatRelative,
isThisWeek,
} from 'date-fns';
import { enGB } from 'date-fns/locale';
import React from 'react';

Expand Down Expand Up @@ -39,11 +45,18 @@ const HabitLastEntryCell = ({ id }: { id: number }) => {
};

return latestOccurrenceTimestamp ? (
<p>
{capitalizeFirstLetter(formatRelativeDate(latestOccurrenceTimestamp))}
</p>
<Tooltip
content={format(new Date(latestOccurrenceTimestamp), 'MMMM do, y')}
color="primary"
showArrow
offset={12}
>
<span>
{capitalizeFirstLetter(formatRelativeDate(latestOccurrenceTimestamp))}
</span>
</Tooltip>
) : (
<p className="text-gray-400">None</p>
<span className="text-gray-400">None</span>
);
};

Expand Down
36 changes: 28 additions & 8 deletions src/components/habit/habits-page/HabitLongestStreakCell.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
import type { Streak } from '@models';
import { Tooltip } from '@nextui-org/react';
import { getLongestHabitStreak } from '@services';
import React from 'react';

const options: Intl.DateTimeFormatOptions = {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
};

const dateTimeFormat = new Intl.DateTimeFormat('en', options);

const HabitLongestStreakCell = ({ id }: { id: number }) => {
const [longestStreakLength, setLongestStreakLength] = React.useState<
number | null
>(null);
const [longestStreak, setLongestStreak] = React.useState<Streak>({
streakLength: null,
streakStart: null,
streakEnd: null,
});

React.useEffect(() => {
getLongestHabitStreak(id).then((streakLength) => {
setLongestStreakLength(streakLength);
getLongestHabitStreak(id).then((longestStreak) => {
setLongestStreak(longestStreak);
});
}, [id]);

return longestStreakLength ? (
<p>{longestStreakLength} days</p>
const range = dateTimeFormat.formatRange(
new Date(longestStreak.streakStart || 0),
new Date(longestStreak.streakEnd || 0)
);

return longestStreak.streakLength ? (
<Tooltip content={range} color="primary" showArrow offset={12}>
<span>{longestStreak.streakLength} days</span>
</Tooltip>
) : (
<p className="text-gray-400">None</p>
<span className="text-gray-400">None</span>
);
};

Expand Down
20 changes: 15 additions & 5 deletions src/components/habit/habits-page/HabitsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ import HabitLastEntryCell from './HabitLastEntryCell';
import HabitLongestStreakCell from './HabitLongestStreakCell';
import HabitsTotalEntriesCell from './HabitsTotalEntriesCell';

const habitColumns = [
type Column = {
key: string;
label: string;
align?: 'start' | 'center' | 'end';
};

const habitColumns: Column[] = [
{
key: 'icon',
label: 'Icon',
Expand Down Expand Up @@ -56,10 +62,12 @@ const habitColumns = [
{
key: 'total-entries',
label: 'Total entries',
align: 'center',
},
{
key: 'actions',
label: 'Actions',
align: 'end',
},
];

Expand Down Expand Up @@ -101,7 +109,7 @@ const HabitsPage = () => {

return (
<>
<h1 className="mx-auto my-4 text-3xl font-bold text-gray-800 dark:text-gray-300">
<h1 className="mx-auto mb-4 mt-8 text-3xl font-bold text-gray-800 dark:text-gray-300">
Your habits
</h1>
<Table
Expand All @@ -116,7 +124,9 @@ const HabitsPage = () => {
>
<TableHeader columns={habitColumns}>
{(column) => (
<TableColumn key={column.key}>{column.label}</TableColumn>
<TableColumn key={column.key} align={column.align || 'start'}>
{column.label}
</TableColumn>
)}
</TableHeader>
<TableBody emptyContent="No habits yet">
Expand Down Expand Up @@ -145,11 +155,11 @@ const HabitsPage = () => {
<TableCell>
<HabitLongestStreakCell id={habit.id} />
</TableCell>
<TableCell>
<TableCell align="center">
<HabitsTotalEntriesCell id={habit.id} />
</TableCell>
<TableCell>
<div className="flex gap-2">
<div className="flex justify-end gap-2">
<Tooltip content="Edit habit">
<Button
isIconOnly
Expand Down
4 changes: 2 additions & 2 deletions src/components/habit/habits-page/HabitsTotalEntriesCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ const HabitsTotalEntriesCell = ({ id }: { id: number }) => {
}, [id]);

return entriesCount ? (
<p>{entriesCount}</p>
<span>{entriesCount}</span>
) : (
<p className="text-gray-400"></p>
<span className="text-gray-400"></span>
);
};

Expand Down
8 changes: 7 additions & 1 deletion src/models/occurrence.model.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import type { CalendarDay } from '@helpers';
import type { CamelCasedPropertiesDeep } from 'type-fest';

import type { TablesInsert, Tables } from '../../supabase/database.types';
import type {
TablesInsert,
Tables,
CompositeTypes,
} from '../../supabase/database.types';

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

export type Streak = CamelCasedPropertiesDeep<CompositeTypes<'streak_info'>>;

type BaseOccurrence = CamelCasedPropertiesDeep<Tables<'occurrences'>>;

type OccurrenceHabit = Pick<Habit, 'name' | 'iconPath'>;
Expand Down
16 changes: 6 additions & 10 deletions src/services/occurrences.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cacheOccurrences, occurrencesCache, supabaseClient } from '@helpers';
import type { Occurrence, OccurrencesInsert } from '@models';
import type { Occurrence, OccurrencesInsert, Streak } from '@models';
import {
transformClientEntity,
transformServerEntities,
Expand Down Expand Up @@ -89,22 +89,18 @@ export const getLatestHabitOccurrenceTimestamp = async (habitId: number) => {
return timestamp;
};

export const getLongestHabitStreak = async (habitId: number) => {
export const getLongestHabitStreak = async (
habitId: number
): Promise<Streak> => {
const { error, data } = await supabaseClient.rpc('get_longest_streak', {
habit_identifier: habitId,
p_habit_id: habitId,
});

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

if (!data?.length) {
return null;
}

const [{ streak_length: streakLength }] = data;

return streakLength;
return transformServerEntity(data);
};

export const getHabitTotalEntries = async (habitId: number) => {
Expand Down
15 changes: 7 additions & 8 deletions supabase/database.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -913,21 +913,20 @@ export type Database = {
Functions: {
get_longest_streak: {
Args: {
habit_identifier: number
p_habit_id: number
}
Returns: {
habit_id: number
streak_start: string
streak_end: string
streak_length: number
}[]
Returns: Database["public"]["CompositeTypes"]["streak_info"]
}
}
Enums: {
[_ in never]: never
}
CompositeTypes: {
[_ in never]: never
streak_info: {
streak_length: number | null
streak_start: number | null
streak_end: number | null
}
}
}
storage: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
DROP FUNCTION IF EXISTS get_longest_streak(integer);
DROP TYPE IF EXISTS streak_info;

CREATE TYPE streak_info AS (
streak_length integer,
streak_start date,
streak_end date
);

CREATE OR REPLACE FUNCTION get_longest_streak(p_habit_id integer)
RETURNS streak_info AS $$
DECLARE
result streak_info;
BEGIN
WITH daily_occurrences AS (
SELECT DISTINCT DATE(to_timestamp(timestamp/1000)) as occurrence_date
FROM occurrences
WHERE habit_id = p_habit_id
ORDER BY occurrence_date
),
streaks AS (
SELECT
occurrence_date,
occurrence_date - (ROW_NUMBER() OVER (ORDER BY occurrence_date))::integer AS streak_group
FROM daily_occurrences
),
streak_lengths AS (
SELECT
streak_group,
COUNT(*) as streak_length,
MIN(occurrence_date) as streak_start,
MAX(occurrence_date) as streak_end
FROM streaks
GROUP BY streak_group
)
SELECT
streak_length,
streak_start,
streak_end
INTO result
FROM streak_lengths
ORDER BY streak_length DESC
LIMIT 1;

IF result.streak_length IS NULL THEN
result.streak_length := 0;
result.streak_start := NULL;
result.streak_end := NULL;
END IF;

RETURN result;
END;
$$ LANGUAGE plpgsql;

0 comments on commit 26dfe80

Please sign in to comment.