mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-01-21 06:34:30 +01:00
added support for tasks
This commit is contained in:
17
lib/atoms.ts
17
lib/atoms.ts
@@ -19,6 +19,15 @@ import {
|
||||
getCompletionsForToday,
|
||||
getISODate
|
||||
} from "@/lib/utils";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
|
||||
export interface BrowserSettings {
|
||||
viewType: ViewType
|
||||
}
|
||||
|
||||
export const browserSettingsAtom = atomWithStorage('browserSettings', {
|
||||
viewType: 'habits'
|
||||
} as BrowserSettings)
|
||||
|
||||
export const settingsAtom = atom(getDefaultSettings());
|
||||
export const habitsAtom = atom(getDefaultHabitsData());
|
||||
@@ -120,11 +129,3 @@ export const pomodoroTodayCompletionsAtom = atom((get) => {
|
||||
timezone: settings.system.timezone
|
||||
})
|
||||
})
|
||||
|
||||
export interface TransientSettings {
|
||||
viewType: ViewType
|
||||
}
|
||||
|
||||
export const transientSettingsAtom = atom<TransientSettings>({
|
||||
viewType: 'habits'
|
||||
})
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { CheckSquare, Target } from "lucide-react"
|
||||
|
||||
export const INITIAL_RECURRENCE_RULE = 'daily'
|
||||
export const INITIAL_DUE = 'today'
|
||||
|
||||
export const RECURRENCE_RULE_MAP: { [key: string]: string } = {
|
||||
'daily': 'FREQ=DAILY',
|
||||
@@ -8,3 +11,11 @@ export const RECURRENCE_RULE_MAP: { [key: string]: string } = {
|
||||
'': 'invalid',
|
||||
}
|
||||
|
||||
export const DUE_MAP: { [key: string]: string } = {
|
||||
'tom': 'tomorrow',
|
||||
'tod': 'today',
|
||||
'yes': 'yesterday',
|
||||
}
|
||||
|
||||
export const HabitIcon = Target
|
||||
export const TaskIcon = CheckSquare;
|
||||
|
||||
@@ -6,6 +6,7 @@ export type Habit = {
|
||||
coinReward: number
|
||||
targetCompletions?: number // Optional field, default to 1
|
||||
completions: string[] // Array of UTC ISO date strings
|
||||
isTask?: boolean // mark the habit as a task
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +19,7 @@ export type WishlistItemType = {
|
||||
coinCost: number
|
||||
}
|
||||
|
||||
export type TransactionType = 'HABIT_COMPLETION' | 'HABIT_UNDO' | 'WISH_REDEMPTION' | 'MANUAL_ADJUSTMENT';
|
||||
export type TransactionType = 'HABIT_COMPLETION' | 'HABIT_UNDO' | 'WISH_REDEMPTION' | 'MANUAL_ADJUSTMENT' | 'TASK_COMPLETION' | 'TASK_UNDO';
|
||||
|
||||
export interface CoinTransaction {
|
||||
id: string;
|
||||
|
||||
33
lib/utils.ts
33
lib/utils.ts
@@ -1,9 +1,10 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { DateTime } from "luxon"
|
||||
import { DateTime, DateTimeFormatOptions } from "luxon"
|
||||
import { datetime, RRule } from 'rrule'
|
||||
import { Freq, Habit, CoinTransaction } from '@/lib/types'
|
||||
import { INITIAL_RECURRENCE_RULE, RECURRENCE_RULE_MAP } from "./constants"
|
||||
import { DUE_MAP, INITIAL_RECURRENCE_RULE, RECURRENCE_RULE_MAP } from "./constants"
|
||||
import * as chrono from 'chrono-node';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
@@ -41,9 +42,13 @@ export function d2t({ dateTime, timezone = 'utc' }: { dateTime: DateTime, timezo
|
||||
}
|
||||
|
||||
// convert datetime object to string, mostly for display
|
||||
export function d2s({ dateTime, format, timezone }: { dateTime: DateTime, format?: string, timezone: string }) {
|
||||
export function d2s({ dateTime, format, timezone }: { dateTime: DateTime, format?: string | DateTimeFormatOptions, timezone: string }) {
|
||||
if (format) {
|
||||
return dateTime.setZone(timezone).toFormat(format);
|
||||
if (typeof format === 'string') {
|
||||
return dateTime.setZone(timezone).toFormat(format);
|
||||
} else {
|
||||
return dateTime.setZone(timezone).toLocaleString(format);
|
||||
}
|
||||
}
|
||||
return dateTime.setZone(timezone).toLocaleString(DateTime.DATETIME_MED);
|
||||
}
|
||||
@@ -204,6 +209,17 @@ export function serializeRRule(rrule: RRule) {
|
||||
return rrule.toString()
|
||||
}
|
||||
|
||||
export function parseNaturalLanguageDate({ text, timezone }: { text: string, timezone: string }) {
|
||||
if (DUE_MAP[text]) {
|
||||
text = DUE_MAP[text]
|
||||
}
|
||||
const now = getNow({ timezone })
|
||||
const due = chrono.parseDate(text, { instant: now.toJSDate(), timezone })
|
||||
if (!due) throw Error('invalid rule')
|
||||
// return d2s({ dateTime: DateTime.fromJSDate(due), timezone, format: DateTime.DATE_MED_WITH_WEEKDAY })
|
||||
return DateTime.fromJSDate(due).setZone(timezone)
|
||||
}
|
||||
|
||||
export function isHabitDue({
|
||||
habit,
|
||||
timezone,
|
||||
@@ -213,6 +229,11 @@ export function isHabitDue({
|
||||
timezone: string
|
||||
date: DateTime
|
||||
}): boolean {
|
||||
if (habit.isTask) {
|
||||
// For tasks, frequency is stored as a UTC ISO timestamp
|
||||
const taskDueDate = t2d({ timestamp: habit.frequency, timezone })
|
||||
return isSameDate(taskDueDate, date);
|
||||
}
|
||||
const startOfDay = date.setZone(timezone).startOf('day')
|
||||
const endOfDay = date.setZone(timezone).endOf('day')
|
||||
|
||||
@@ -244,6 +265,10 @@ export function isHabitDueToday({
|
||||
}
|
||||
|
||||
export function getHabitFreq(habit: Habit): Freq {
|
||||
if (habit.isTask) {
|
||||
// don't support recurring task yet
|
||||
return 'daily'
|
||||
}
|
||||
const rrule = parseRRule(habit.frequency)
|
||||
const freq = rrule.origOptions.freq
|
||||
switch (freq) {
|
||||
|
||||
Reference in New Issue
Block a user