mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-01-21 06:34:30 +01:00
fix: removed viewType from browser Settings Atom, converted to using path to identify pages
This commit is contained in:
@@ -3,7 +3,7 @@ import HabitList from '@/components/HabitList'
|
|||||||
export default function HabitsPage() {
|
export default function HabitsPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<HabitList />
|
<HabitList isTasksView={false} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
10
app/tasks/page.tsx
Normal file
10
app/tasks/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import HabitList from '@/components/HabitList'
|
||||||
|
|
||||||
|
export default function TasksPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<HabitList isTasksView={true} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -220,12 +220,6 @@ const ItemSection = ({
|
|||||||
<Link
|
<Link
|
||||||
href={`/habits?highlight=${habit.id}`}
|
href={`/habits?highlight=${habit.id}`}
|
||||||
className="flex items-center gap-1 hover:text-primary transition-colors"
|
className="flex items-center gap-1 hover:text-primary transition-colors"
|
||||||
onClick={() => {
|
|
||||||
const newViewType = isTask ? 'tasks' : 'habits';
|
|
||||||
if (browserSettings.viewType !== newViewType) {
|
|
||||||
setBrowserSettings(prev => ({ ...prev, viewType: newViewType }));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{isTask && isTaskOverdue(habit, settings.system.timezone) && !isCompleted && (
|
{isTask && isTaskOverdue(habit, settings.system.timezone) && !isCompleted && (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
@@ -314,12 +308,6 @@ const ItemSection = ({
|
|||||||
<Link
|
<Link
|
||||||
href={viewLink}
|
href={viewLink}
|
||||||
className="text-sm text-muted-foreground hover:text-primary flex items-center gap-1"
|
className="text-sm text-muted-foreground hover:text-primary flex items-center gap-1"
|
||||||
onClick={() => {
|
|
||||||
const newViewType = isTask ? 'tasks' : 'habits';
|
|
||||||
if (browserSettings.viewType !== newViewType) {
|
|
||||||
setBrowserSettings(prev => ({ ...prev, viewType: newViewType }));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
<ArrowRight className="h-3 w-3" />
|
<ArrowRight className="h-3 w-3" />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
DropdownMenuTrigger
|
DropdownMenuTrigger
|
||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { useHabits } from '@/hooks/useHabits'
|
import { useHabits } from '@/hooks/useHabits'
|
||||||
import { browserSettingsAtom, settingsAtom, usersAtom } from '@/lib/atoms'
|
import { settingsAtom, usersAtom } from '@/lib/atoms'
|
||||||
import { useHelpers } from '@/lib/client-helpers'
|
import { useHelpers } from '@/lib/client-helpers'
|
||||||
import { Habit, User } from '@/lib/types'
|
import { Habit, User } from '@/lib/types'
|
||||||
import { convertMachineReadableFrequencyToHumanReadable, getCompletionsForToday, isTaskOverdue } from '@/lib/utils'
|
import { convertMachineReadableFrequencyToHumanReadable, getCompletionsForToday, isTaskOverdue } from '@/lib/utils'
|
||||||
@@ -15,6 +15,7 @@ import { Check, Coins, Edit, MoreVertical, Pin, Undo2 } from 'lucide-react'; //
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { HabitContextMenuItems } from './HabitContextMenuItems'
|
import { HabitContextMenuItems } from './HabitContextMenuItems'
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar'
|
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar'
|
||||||
|
import { usePathname } from 'next/navigation'
|
||||||
|
|
||||||
interface HabitItemProps {
|
interface HabitItemProps {
|
||||||
habit: Habit
|
habit: Habit
|
||||||
@@ -53,9 +54,7 @@ export default function HabitItem({ habit, onEdit, onDelete }: HabitItemProps) {
|
|||||||
const { currentUser, hasPermission } = useHelpers()
|
const { currentUser, hasPermission } = useHelpers()
|
||||||
const canWrite = hasPermission('habit', 'write')
|
const canWrite = hasPermission('habit', 'write')
|
||||||
const canInteract = hasPermission('habit', 'interact')
|
const canInteract = hasPermission('habit', 'interact')
|
||||||
const [browserSettings] = useAtom(browserSettingsAtom)
|
const pathname = usePathname();
|
||||||
const isTasksView = browserSettings.viewType === 'tasks'
|
|
||||||
const isRecurRule = !isTasksView
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
@@ -83,7 +82,7 @@ export default function HabitItem({ habit, onEdit, onDelete }: HabitItemProps) {
|
|||||||
>
|
>
|
||||||
<CardHeader className="flex-none">
|
<CardHeader className="flex-none">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<CardTitle className={`line-clamp-1 ${habit.archived ? 'text-gray-400 dark:text-gray-500' : ''} flex items-center ${isTasksView ? 'w-full' : ''} justify-between`}>
|
<CardTitle className={`line-clamp-1 ${habit.archived ? 'text-gray-400 dark:text-gray-500' : ''} flex items-center ${pathname.includes("tasks") ? 'w-full' : ''} justify-between`}>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{habit.pinned && (
|
{habit.pinned && (
|
||||||
<Pin className="h-4 w-4 text-yellow-500" />
|
<Pin className="h-4 w-4 text-yellow-500" />
|
||||||
@@ -108,7 +107,7 @@ export default function HabitItem({ habit, onEdit, onDelete }: HabitItemProps) {
|
|||||||
<p className={`text-sm ${habit.archived ? 'text-gray-400 dark:text-gray-500' : 'text-gray-500'}`}>
|
<p className={`text-sm ${habit.archived ? 'text-gray-400 dark:text-gray-500' : 'text-gray-500'}`}>
|
||||||
When: {convertMachineReadableFrequencyToHumanReadable({
|
When: {convertMachineReadableFrequencyToHumanReadable({
|
||||||
frequency: habit.frequency,
|
frequency: habit.frequency,
|
||||||
isRecurRule,
|
isRecurRule: pathname.includes("habits"),
|
||||||
timezone: settings.system.timezone
|
timezone: settings.system.timezone
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Input } from '@/components/ui/input'; // Added
|
|||||||
import { Label } from '@/components/ui/label'; // Added
|
import { Label } from '@/components/ui/label'; // Added
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; // Added
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; // Added
|
||||||
import { useHabits } from '@/hooks/useHabits'
|
import { useHabits } from '@/hooks/useHabits'
|
||||||
import { browserSettingsAtom, habitsAtom } from '@/lib/atoms'
|
import { habitsAtom } from '@/lib/atoms'
|
||||||
import { HabitIcon, TaskIcon } from '@/lib/constants'
|
import { HabitIcon, TaskIcon } from '@/lib/constants'
|
||||||
import { Habit } from '@/lib/types'
|
import { Habit } from '@/lib/types'
|
||||||
import { getHabitFreq } from '@/lib/utils'; // Added
|
import { getHabitFreq } from '@/lib/utils'; // Added
|
||||||
@@ -19,12 +19,9 @@ import EmptyState from './EmptyState'
|
|||||||
import HabitItem from './HabitItem'
|
import HabitItem from './HabitItem'
|
||||||
import { ViewToggle } from './ViewToggle'
|
import { ViewToggle } from './ViewToggle'
|
||||||
|
|
||||||
export default function HabitList() {
|
export default function HabitList({ isTasksView }: { isTasksView: boolean}) {
|
||||||
const { saveHabit, deleteHabit } = useHabits()
|
const { saveHabit, deleteHabit } = useHabits()
|
||||||
const [habitsData] = useAtom(habitsAtom) // setHabitsData removed as it's not used
|
const [habitsData] = useAtom(habitsAtom) // setHabitsData removed as it's not used
|
||||||
const [browserSettings] = useAtom(browserSettingsAtom)
|
|
||||||
const isTasksView = browserSettings.viewType === 'tasks'
|
|
||||||
// const [settings] = useAtom(settingsAtom); // settingsAtom is not directly used in HabitList itself.
|
|
||||||
|
|
||||||
type SortableField = 'name' | 'coinReward' | 'dueDate' | 'frequency';
|
type SortableField = 'name' | 'coinReward' | 'dueDate' | 'frequency';
|
||||||
type SortOrder = 'asc' | 'desc';
|
type SortOrder = 'asc' | 'desc';
|
||||||
@@ -131,9 +128,6 @@ export default function HabitList() {
|
|||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='py-4'>
|
|
||||||
<ViewToggle />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Search and Sort Controls */}
|
{/* Search and Sort Controls */}
|
||||||
<div className="flex flex-col sm:flex-row items-center gap-4 my-4">
|
<div className="flex flex-col sm:flex-row items-center gap-4 my-4">
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { browserSettingsAtom } from '@/lib/atoms'
|
|
||||||
import { useHelpers } from '@/lib/client-helpers'
|
import { useHelpers } from '@/lib/client-helpers'
|
||||||
import { HabitIcon, TaskIcon } from '@/lib/constants'
|
import { HabitIcon, TaskIcon } from '@/lib/constants'
|
||||||
import { useAtom } from 'jotai'
|
|
||||||
import { Calendar, Coins, Gift, Home } from 'lucide-react'
|
import { Calendar, Coins, Gift, Home } from 'lucide-react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
@@ -12,32 +10,24 @@ import { usePathname } from 'next/navigation'
|
|||||||
|
|
||||||
type ViewPort = 'main' | 'mobile'
|
type ViewPort = 'main' | 'mobile'
|
||||||
|
|
||||||
const navItems = (isTasksView: boolean) => [
|
const navItems = () => [
|
||||||
{ icon: Home, label: 'Dashboard', href: '/', position: 'main' },
|
{ icon: Home, label: 'Dashboard', href: '/', position: 'main' },
|
||||||
{
|
{ icon: HabitIcon, label: 'Habits', href: '/habits', position: 'main' },
|
||||||
icon: isTasksView ? TaskIcon : HabitIcon,
|
{ icon: TaskIcon, label: 'Tasks', href: '/tasks', position: 'main' },
|
||||||
label: isTasksView ? 'Tasks' : 'Habits',
|
|
||||||
href: '/habits',
|
|
||||||
position: 'main'
|
|
||||||
},
|
|
||||||
{ icon: Calendar, label: 'Calendar', href: '/calendar', position: 'main' },
|
{ icon: Calendar, label: 'Calendar', href: '/calendar', position: 'main' },
|
||||||
{ icon: Gift, label: 'Wishlist', href: '/wishlist', position: 'main' },
|
{ icon: Gift, label: 'Wishlist', href: '/wishlist', position: 'main' },
|
||||||
{ icon: Coins, label: 'Coins', href: '/coins', position: 'main' },
|
{ icon: Coins, label: 'Coins', href: '/coins', position: 'main' },
|
||||||
]
|
]
|
||||||
|
|
||||||
interface NavigationProps {
|
interface NavigationProps {
|
||||||
className?: string
|
|
||||||
viewPort: ViewPort
|
viewPort: ViewPort
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Navigation({ className, viewPort }: NavigationProps) {
|
export default function Navigation({ viewPort }: NavigationProps) {
|
||||||
const [showAbout, setShowAbout] = useState(false)
|
const [showAbout, setShowAbout] = useState(false)
|
||||||
const [isMobileView, setIsMobileView] = useState(false)
|
const [isMobileView, setIsMobileView] = useState(false)
|
||||||
const [browserSettings] = useAtom(browserSettingsAtom)
|
|
||||||
const isTasksView = browserSettings.viewType === 'tasks'
|
|
||||||
const { isIOS } = useHelpers()
|
const { isIOS } = useHelpers()
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
console.log(pathname, pathname === navItems(false)[1].href)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
@@ -59,8 +49,8 @@ export default function Navigation({ className, viewPort }: NavigationProps) {
|
|||||||
<>
|
<>
|
||||||
<div className={isIOS ? "pb-20" : "pb-16"} /> {/* Add padding at the bottom to prevent content from being hidden */}
|
<div className={isIOS ? "pb-20" : "pb-16"} /> {/* Add padding at the bottom to prevent content from being hidden */}
|
||||||
<nav className={`lg:hidden fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-800 shadow-lg ${isIOS ? "pb-4" : ""}`}>
|
<nav className={`lg:hidden fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-800 shadow-lg ${isIOS ? "pb-4" : ""}`}>
|
||||||
<div className="grid grid-cols-5 w-full">
|
<div className="grid grid-cols-6 w-full">
|
||||||
{[...navItems(isTasksView).filter(item => item.position === 'main'), ...navItems(isTasksView).filter(item => item.position === 'bottom')].map((item) => (
|
{...navItems().map((item) => (
|
||||||
<Link
|
<Link
|
||||||
key={item.label}
|
key={item.label}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
@@ -88,7 +78,7 @@ export default function Navigation({ className, viewPort }: NavigationProps) {
|
|||||||
<div className="flex flex-col h-0 flex-1 bg-gray-800">
|
<div className="flex flex-col h-0 flex-1 bg-gray-800">
|
||||||
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
|
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
|
||||||
<nav className="mt-5 flex-1 px-2 space-y-1">
|
<nav className="mt-5 flex-1 px-2 space-y-1">
|
||||||
{navItems(isTasksView).filter(item => item.position === 'main').map((item) => (
|
{navItems().filter(item => item.position === 'main').map((item) => (
|
||||||
<Link
|
<Link
|
||||||
key={item.label}
|
key={item.label}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
|
|||||||
@@ -2,30 +2,26 @@
|
|||||||
|
|
||||||
import { browserSettingsAtom, habitsAtom, settingsAtom } from '@/lib/atoms'
|
import { browserSettingsAtom, habitsAtom, settingsAtom } from '@/lib/atoms'
|
||||||
import { HabitIcon, TaskIcon } from '@/lib/constants'
|
import { HabitIcon, TaskIcon } from '@/lib/constants'
|
||||||
import type { ViewType } from '@/lib/types'
|
|
||||||
import { cn, isHabitDueToday } from '@/lib/utils'
|
import { cn, isHabitDueToday } from '@/lib/utils'
|
||||||
import { useAtom } from 'jotai'
|
import { useAtom } from 'jotai'
|
||||||
import { NotificationBadge } from './ui/notification-badge'
|
import { NotificationBadge } from './ui/notification-badge'
|
||||||
|
import { usePathname, useRouter } from 'next/navigation'
|
||||||
|
|
||||||
interface ViewToggleProps {
|
interface ViewToggleProps {
|
||||||
defaultView?: ViewType
|
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ViewToggle({
|
export function ViewToggle({
|
||||||
defaultView = 'habits',
|
|
||||||
className
|
className
|
||||||
}: ViewToggleProps) {
|
}: ViewToggleProps) {
|
||||||
const [browserSettings, setBrowserSettings] = useAtom(browserSettingsAtom)
|
const [browserSettings, setBrowserSettings] = useAtom(browserSettingsAtom)
|
||||||
const [habits] = useAtom(habitsAtom)
|
const [habits] = useAtom(habitsAtom)
|
||||||
const [settings] = useAtom(settingsAtom)
|
const [settings] = useAtom(settingsAtom)
|
||||||
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const handleViewChange = (checked: boolean) => {
|
const handleViewChange = () => {
|
||||||
const newView = checked ? 'tasks' : 'habits'
|
router.push(pathname.includes("habits") ? "/tasks" : "/habits");
|
||||||
setBrowserSettings({
|
|
||||||
...browserSettings,
|
|
||||||
viewType: newView,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate due tasks count
|
// Calculate due tasks count
|
||||||
@@ -37,10 +33,10 @@ export function ViewToggle({
|
|||||||
<div className={cn('inline-flex rounded-full bg-muted/50 h-8', className)}>
|
<div className={cn('inline-flex rounded-full bg-muted/50 h-8', className)}>
|
||||||
<div className="relative flex gap-0.5 rounded-full bg-background p-0.5 h-full">
|
<div className="relative flex gap-0.5 rounded-full bg-background p-0.5 h-full">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleViewChange(false)}
|
onClick={handleViewChange}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative z-10 rounded-full px-4 py-2 text-sm font-medium transition-colors flex items-center gap-2',
|
'relative z-10 rounded-full px-4 py-2 text-sm font-medium transition-colors flex items-center gap-2',
|
||||||
browserSettings.viewType === 'habits' ? 'text-primary-foreground' : 'text-muted-foreground hover:text-foreground'
|
pathname.includes('habits') ? 'text-primary-foreground' : 'text-muted-foreground hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<HabitIcon className="h-4 w-4" />
|
<HabitIcon className="h-4 w-4" />
|
||||||
@@ -49,14 +45,14 @@ export function ViewToggle({
|
|||||||
<NotificationBadge
|
<NotificationBadge
|
||||||
label={dueTasksCount}
|
label={dueTasksCount}
|
||||||
show={dueTasksCount > 0}
|
show={dueTasksCount > 0}
|
||||||
variant={browserSettings.viewType === 'tasks' ? 'secondary' : 'default'}
|
variant={pathname.includes('tasks') ? 'secondary' : 'default'}
|
||||||
className="shadow-md"
|
className="shadow-md"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleViewChange(true)}
|
onClick={handleViewChange}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative z-10 rounded-full px-4 py-2 text-sm font-medium transition-colors flex items-center gap-2',
|
'relative z-10 rounded-full px-4 py-2 text-sm font-medium transition-colors flex items-center gap-2',
|
||||||
browserSettings.viewType === 'tasks' ? 'text-primary-foreground' : 'text-muted-foreground hover:text-foreground'
|
pathname.includes('tasks') ? 'text-primary-foreground' : 'text-muted-foreground hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<TaskIcon className="h-4 w-4" />
|
<TaskIcon className="h-4 w-4" />
|
||||||
@@ -66,7 +62,7 @@ export function ViewToggle({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute left-0.5 top-0.5 h-[calc(100%-0.25rem)] rounded-full bg-primary transition-transform',
|
'absolute left-0.5 top-0.5 h-[calc(100%-0.25rem)] rounded-full bg-primary transition-transform',
|
||||||
browserSettings.viewType === 'habits' ? 'w-[calc(50%-0.125rem)]' : 'w-[calc(50%-0.125rem)] translate-x-[calc(100%+0.125rem)]'
|
pathname.includes('habits') ? 'w-[calc(50%-0.125rem)]' : 'w-[calc(50%-0.125rem)] translate-x-[calc(100%+0.125rem)]'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,19 +22,16 @@ import {
|
|||||||
getDefaultSettings,
|
getDefaultSettings,
|
||||||
getDefaultUsersData,
|
getDefaultUsersData,
|
||||||
getDefaultWishlistData,
|
getDefaultWishlistData,
|
||||||
Habit,
|
Habit
|
||||||
ViewType
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export interface BrowserSettings {
|
export interface BrowserSettings {
|
||||||
viewType: ViewType
|
|
||||||
expandedHabits: boolean
|
expandedHabits: boolean
|
||||||
expandedTasks: boolean
|
expandedTasks: boolean
|
||||||
expandedWishlist: boolean
|
expandedWishlist: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const browserSettingsAtom = atomWithStorage('browserSettings', {
|
export const browserSettingsAtom = atomWithStorage('browserSettings', {
|
||||||
viewType: 'habits',
|
|
||||||
expandedHabits: false,
|
expandedHabits: false,
|
||||||
expandedTasks: false,
|
expandedTasks: false,
|
||||||
expandedWishlist: false
|
expandedWishlist: false
|
||||||
|
|||||||
@@ -181,8 +181,6 @@ export type CompletionCache = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ViewType = 'habits' | 'tasks'
|
|
||||||
|
|
||||||
export interface JotaiHydrateInitialValues {
|
export interface JotaiHydrateInitialValues {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
coins: CoinsData;
|
coins: CoinsData;
|
||||||
|
|||||||
Reference in New Issue
Block a user