create-arete-workspace 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/bin/arete.js +156 -0
- package/bin/create.js +111 -0
- package/lib/install-openclaw.js +50 -0
- package/lib/scaffold.js +213 -0
- package/lib/setup-wizard.js +88 -0
- package/lib/updater.js +130 -0
- package/package.json +34 -0
- package/packages/gatsaeng-os/README.md +36 -0
- package/packages/gatsaeng-os/components.json +23 -0
- package/packages/gatsaeng-os/eslint.config.mjs +18 -0
- package/packages/gatsaeng-os/next.config.ts +7 -0
- package/packages/gatsaeng-os/package.json +59 -0
- package/packages/gatsaeng-os/postcss.config.mjs +7 -0
- package/packages/gatsaeng-os/public/file.svg +1 -0
- package/packages/gatsaeng-os/public/globe.svg +1 -0
- package/packages/gatsaeng-os/public/next.svg +1 -0
- package/packages/gatsaeng-os/public/vercel.svg +1 -0
- package/packages/gatsaeng-os/public/window.svg +1 -0
- package/packages/gatsaeng-os/python/api_server.py +248 -0
- package/packages/gatsaeng-os/python/briefing.py +145 -0
- package/packages/gatsaeng-os/python/config.py +55 -0
- package/packages/gatsaeng-os/python/goal_context_agent.py +193 -0
- package/packages/gatsaeng-os/python/gyeokguk.py +171 -0
- package/packages/gatsaeng-os/python/proactive.py +158 -0
- package/packages/gatsaeng-os/python/requirements.txt +11 -0
- package/packages/gatsaeng-os/python/run.py +28 -0
- package/packages/gatsaeng-os/python/scoring.py +44 -0
- package/packages/gatsaeng-os/python/streak.py +70 -0
- package/packages/gatsaeng-os/python/telegram_bot.py +331 -0
- package/packages/gatsaeng-os/python/timing_engine.py +117 -0
- package/packages/gatsaeng-os/python/vault_io.py +423 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/areas/[id]/page.tsx +215 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/areas/page.tsx +161 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/books/[id]/page.tsx +215 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/books/page.tsx +268 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/calendar/page.tsx +379 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/error.tsx +30 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/focus/page.tsx +293 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/goals/[id]/page.tsx +426 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/goals/page.tsx +178 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/layout.tsx +29 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/notes/[id]/page.tsx +147 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/notes/page.tsx +254 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/page.tsx +26 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/projects/[id]/page.tsx +86 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/projects/page.tsx +215 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/review/page.tsx +475 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/routines/page.tsx +436 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/tasks/[id]/page.tsx +210 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/tasks/page.tsx +307 -0
- package/packages/gatsaeng-os/src/app/(dashboard)/voice/page.tsx +212 -0
- package/packages/gatsaeng-os/src/app/api/areas/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/areas/route.ts +22 -0
- package/packages/gatsaeng-os/src/app/api/auth/login/route.ts +52 -0
- package/packages/gatsaeng-os/src/app/api/auth/logout/route.ts +8 -0
- package/packages/gatsaeng-os/src/app/api/books/[id]/route.ts +27 -0
- package/packages/gatsaeng-os/src/app/api/books/route.ts +20 -0
- package/packages/gatsaeng-os/src/app/api/calendar/[id]/route.ts +24 -0
- package/packages/gatsaeng-os/src/app/api/calendar/import/route.ts +52 -0
- package/packages/gatsaeng-os/src/app/api/calendar/route.ts +37 -0
- package/packages/gatsaeng-os/src/app/api/daily/route.ts +51 -0
- package/packages/gatsaeng-os/src/app/api/goals/[id]/route.ts +34 -0
- package/packages/gatsaeng-os/src/app/api/goals/route.ts +30 -0
- package/packages/gatsaeng-os/src/app/api/logs/energy/route.ts +40 -0
- package/packages/gatsaeng-os/src/app/api/logs/focus/route.ts +22 -0
- package/packages/gatsaeng-os/src/app/api/logs/routine/route.ts +54 -0
- package/packages/gatsaeng-os/src/app/api/milestones/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/milestones/route.ts +47 -0
- package/packages/gatsaeng-os/src/app/api/notes/[id]/route.ts +29 -0
- package/packages/gatsaeng-os/src/app/api/notes/route.ts +37 -0
- package/packages/gatsaeng-os/src/app/api/profile/route.ts +17 -0
- package/packages/gatsaeng-os/src/app/api/projects/[id]/route.ts +27 -0
- package/packages/gatsaeng-os/src/app/api/projects/route.ts +25 -0
- package/packages/gatsaeng-os/src/app/api/reviews/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/reviews/route.ts +29 -0
- package/packages/gatsaeng-os/src/app/api/routines/[id]/route.ts +26 -0
- package/packages/gatsaeng-os/src/app/api/routines/route.ts +28 -0
- package/packages/gatsaeng-os/src/app/api/tasks/[id]/route.ts +28 -0
- package/packages/gatsaeng-os/src/app/api/tasks/route.ts +66 -0
- package/packages/gatsaeng-os/src/app/api/timing/current/route.ts +63 -0
- package/packages/gatsaeng-os/src/app/api/voice/chat/route.ts +50 -0
- package/packages/gatsaeng-os/src/app/api/voice/transcribe/route.ts +25 -0
- package/packages/gatsaeng-os/src/app/api/voice/tts/route.ts +36 -0
- package/packages/gatsaeng-os/src/app/error.tsx +30 -0
- package/packages/gatsaeng-os/src/app/favicon.ico +0 -0
- package/packages/gatsaeng-os/src/app/globals.css +208 -0
- package/packages/gatsaeng-os/src/app/layout.tsx +33 -0
- package/packages/gatsaeng-os/src/app/login/page.tsx +87 -0
- package/packages/gatsaeng-os/src/app/providers.tsx +27 -0
- package/packages/gatsaeng-os/src/components/ErrorBoundary.tsx +46 -0
- package/packages/gatsaeng-os/src/components/dashboard/DashboardGrid.tsx +86 -0
- package/packages/gatsaeng-os/src/components/dashboard/DdayWidget.tsx +88 -0
- package/packages/gatsaeng-os/src/components/dashboard/EnergyTracker.tsx +87 -0
- package/packages/gatsaeng-os/src/components/dashboard/FocusTimer.tsx +139 -0
- package/packages/gatsaeng-os/src/components/dashboard/GatsaengScore.tsx +30 -0
- package/packages/gatsaeng-os/src/components/dashboard/GoalRings.tsx +107 -0
- package/packages/gatsaeng-os/src/components/dashboard/ProactiveBar.tsx +98 -0
- package/packages/gatsaeng-os/src/components/dashboard/RoutineChecklist.tsx +81 -0
- package/packages/gatsaeng-os/src/components/dashboard/TimingWidget.tsx +86 -0
- package/packages/gatsaeng-os/src/components/dashboard/WidgetCustomizer.tsx +95 -0
- package/packages/gatsaeng-os/src/components/dashboard/WidgetWrapper.tsx +33 -0
- package/packages/gatsaeng-os/src/components/dashboard/ZeigarnikPanel.tsx +43 -0
- package/packages/gatsaeng-os/src/components/editor/EditorToolbar.tsx +186 -0
- package/packages/gatsaeng-os/src/components/editor/TiptapEditor.tsx +114 -0
- package/packages/gatsaeng-os/src/components/layout/Header.tsx +47 -0
- package/packages/gatsaeng-os/src/components/layout/MobileBottomNav.tsx +122 -0
- package/packages/gatsaeng-os/src/components/layout/MobileSidebar.tsx +29 -0
- package/packages/gatsaeng-os/src/components/layout/Sidebar.tsx +142 -0
- package/packages/gatsaeng-os/src/components/onboarding/OnboardingFlow.tsx +229 -0
- package/packages/gatsaeng-os/src/components/onboarding/OnboardingGate.tsx +78 -0
- package/packages/gatsaeng-os/src/components/projects/CalendarView.tsx +152 -0
- package/packages/gatsaeng-os/src/components/projects/KanbanView.tsx +180 -0
- package/packages/gatsaeng-os/src/components/projects/ListView.tsx +82 -0
- package/packages/gatsaeng-os/src/components/projects/TableView.tsx +206 -0
- package/packages/gatsaeng-os/src/components/projects/TaskCard.tsx +154 -0
- package/packages/gatsaeng-os/src/components/projects/TaskForm.tsx +128 -0
- package/packages/gatsaeng-os/src/components/projects/ViewSwitcher.tsx +40 -0
- package/packages/gatsaeng-os/src/components/search/GlobalSearch.tsx +179 -0
- package/packages/gatsaeng-os/src/components/shared/InlineEdit.tsx +77 -0
- package/packages/gatsaeng-os/src/components/shared/PinButton.tsx +42 -0
- package/packages/gatsaeng-os/src/components/tasks/DDayBadge.tsx +34 -0
- package/packages/gatsaeng-os/src/components/ui/badge.tsx +48 -0
- package/packages/gatsaeng-os/src/components/ui/button.tsx +64 -0
- package/packages/gatsaeng-os/src/components/ui/card.tsx +92 -0
- package/packages/gatsaeng-os/src/components/ui/checkbox.tsx +32 -0
- package/packages/gatsaeng-os/src/components/ui/command.tsx +184 -0
- package/packages/gatsaeng-os/src/components/ui/dialog.tsx +158 -0
- package/packages/gatsaeng-os/src/components/ui/input.tsx +21 -0
- package/packages/gatsaeng-os/src/components/ui/label.tsx +24 -0
- package/packages/gatsaeng-os/src/components/ui/popover.tsx +89 -0
- package/packages/gatsaeng-os/src/components/ui/progress.tsx +31 -0
- package/packages/gatsaeng-os/src/components/ui/select.tsx +190 -0
- package/packages/gatsaeng-os/src/components/ui/sheet.tsx +143 -0
- package/packages/gatsaeng-os/src/components/ui/tabs.tsx +91 -0
- package/packages/gatsaeng-os/src/components/ui/toggle-group.tsx +83 -0
- package/packages/gatsaeng-os/src/components/ui/toggle.tsx +47 -0
- package/packages/gatsaeng-os/src/components/ui/tooltip.tsx +57 -0
- package/packages/gatsaeng-os/src/hooks/useAreas.ts +53 -0
- package/packages/gatsaeng-os/src/hooks/useBooks.ts +62 -0
- package/packages/gatsaeng-os/src/hooks/useCalendar.ts +59 -0
- package/packages/gatsaeng-os/src/hooks/useDaily.ts +15 -0
- package/packages/gatsaeng-os/src/hooks/useGlobalTasks.ts +45 -0
- package/packages/gatsaeng-os/src/hooks/useGoals.ts +53 -0
- package/packages/gatsaeng-os/src/hooks/useMilestones.ts +75 -0
- package/packages/gatsaeng-os/src/hooks/useNotes.ts +65 -0
- package/packages/gatsaeng-os/src/hooks/useProjects.ts +102 -0
- package/packages/gatsaeng-os/src/hooks/useRoutines.ts +76 -0
- package/packages/gatsaeng-os/src/hooks/useTiming.ts +27 -0
- package/packages/gatsaeng-os/src/lib/apiFetch.ts +14 -0
- package/packages/gatsaeng-os/src/lib/auth.ts +32 -0
- package/packages/gatsaeng-os/src/lib/date.ts +7 -0
- package/packages/gatsaeng-os/src/lib/editor/markdown.ts +35 -0
- package/packages/gatsaeng-os/src/lib/llm-governor.ts +167 -0
- package/packages/gatsaeng-os/src/lib/neuroscience/energyCycle.ts +35 -0
- package/packages/gatsaeng-os/src/lib/neuroscience/habitStack.ts +22 -0
- package/packages/gatsaeng-os/src/lib/neuroscience/scoring.ts +32 -0
- package/packages/gatsaeng-os/src/lib/routes.ts +15 -0
- package/packages/gatsaeng-os/src/lib/utils.ts +6 -0
- package/packages/gatsaeng-os/src/lib/vault/config.ts +29 -0
- package/packages/gatsaeng-os/src/lib/vault/frontmatter.ts +84 -0
- package/packages/gatsaeng-os/src/lib/vault/index.ts +180 -0
- package/packages/gatsaeng-os/src/lib/vault/schemas.ts +274 -0
- package/packages/gatsaeng-os/src/middleware.ts +34 -0
- package/packages/gatsaeng-os/src/stores/dashboardStore.ts +26 -0
- package/packages/gatsaeng-os/src/stores/favoritesStore.ts +47 -0
- package/packages/gatsaeng-os/src/stores/timerStore.ts +65 -0
- package/packages/gatsaeng-os/src/types/index.ts +320 -0
- package/packages/gatsaeng-os/tsconfig.json +34 -0
- package/templates/scripts/forge_qa.sh.tmpl +237 -0
- package/templates/scripts/forge_ship.sh.tmpl +183 -0
- package/templates/scripts/session_indexer.py.tmpl +420 -0
- package/templates/scripts/tracer.py.tmpl +266 -0
- package/templates/workspace/AGENTS.md.tmpl +190 -0
- package/templates/workspace/BOOTSTRAP.md.tmpl +27 -0
- package/templates/workspace/HEARTBEAT.md.tmpl +23 -0
- package/templates/workspace/MEMORY.md.tmpl +35 -0
- package/templates/workspace/SOUL.md.tmpl +258 -0
- package/templates/workspace/TOOLS.md.tmpl +28 -0
- package/templates/workspace/USER.md.tmpl +43 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function TooltipProvider({
|
|
9
|
+
delayDuration = 0,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
12
|
+
return (
|
|
13
|
+
<TooltipPrimitive.Provider
|
|
14
|
+
data-slot="tooltip-provider"
|
|
15
|
+
delayDuration={delayDuration}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Tooltip({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
24
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function TooltipTrigger({
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
30
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function TooltipContent({
|
|
34
|
+
className,
|
|
35
|
+
sideOffset = 0,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
39
|
+
return (
|
|
40
|
+
<TooltipPrimitive.Portal>
|
|
41
|
+
<TooltipPrimitive.Content
|
|
42
|
+
data-slot="tooltip-content"
|
|
43
|
+
sideOffset={sideOffset}
|
|
44
|
+
className={cn(
|
|
45
|
+
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
|
|
52
|
+
</TooltipPrimitive.Content>
|
|
53
|
+
</TooltipPrimitive.Portal>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
4
|
+
import { apiFetch } from '@/lib/apiFetch'
|
|
5
|
+
import type { Area } from '@/types'
|
|
6
|
+
|
|
7
|
+
export function useAreas() {
|
|
8
|
+
return useQuery({
|
|
9
|
+
queryKey: ['areas'],
|
|
10
|
+
queryFn: () => apiFetch<Area[]>('/api/areas'),
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useCreateArea() {
|
|
15
|
+
const queryClient = useQueryClient()
|
|
16
|
+
return useMutation({
|
|
17
|
+
mutationFn: (data: Partial<Area>) =>
|
|
18
|
+
apiFetch<Area>('/api/areas', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify(data),
|
|
22
|
+
}),
|
|
23
|
+
onSuccess: () => {
|
|
24
|
+
queryClient.invalidateQueries({ queryKey: ['areas'] })
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useUpdateArea() {
|
|
30
|
+
const queryClient = useQueryClient()
|
|
31
|
+
return useMutation({
|
|
32
|
+
mutationFn: ({ id, ...data }: Partial<Area> & { id: string }) =>
|
|
33
|
+
apiFetch<Area>(`/api/areas/${id}`, {
|
|
34
|
+
method: 'PUT',
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
body: JSON.stringify(data),
|
|
37
|
+
}),
|
|
38
|
+
onSuccess: () => {
|
|
39
|
+
queryClient.invalidateQueries({ queryKey: ['areas'] })
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function useDeleteArea() {
|
|
45
|
+
const queryClient = useQueryClient()
|
|
46
|
+
return useMutation({
|
|
47
|
+
mutationFn: (id: string) =>
|
|
48
|
+
apiFetch(`/api/areas/${id}`, { method: 'DELETE' }),
|
|
49
|
+
onSuccess: () => {
|
|
50
|
+
queryClient.invalidateQueries({ queryKey: ['areas'] })
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
4
|
+
import { apiFetch } from '@/lib/apiFetch'
|
|
5
|
+
import type { Book } from '@/types'
|
|
6
|
+
|
|
7
|
+
export function useBook(id: string) {
|
|
8
|
+
return useQuery({
|
|
9
|
+
queryKey: ['book', id],
|
|
10
|
+
queryFn: () => apiFetch<Book & { _content?: string }>(`/api/books/${id}`),
|
|
11
|
+
enabled: !!id,
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useBooks() {
|
|
16
|
+
return useQuery({
|
|
17
|
+
queryKey: ['books'],
|
|
18
|
+
queryFn: () => apiFetch<Book[]>('/api/books'),
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useCreateBook() {
|
|
23
|
+
const queryClient = useQueryClient()
|
|
24
|
+
return useMutation({
|
|
25
|
+
mutationFn: (data: Partial<Book>) =>
|
|
26
|
+
apiFetch<Book>('/api/books', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'Content-Type': 'application/json' },
|
|
29
|
+
body: JSON.stringify(data),
|
|
30
|
+
}),
|
|
31
|
+
onSuccess: () => {
|
|
32
|
+
queryClient.invalidateQueries({ queryKey: ['books'] })
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function useUpdateBook() {
|
|
38
|
+
const queryClient = useQueryClient()
|
|
39
|
+
return useMutation({
|
|
40
|
+
mutationFn: ({ id, ...data }: Partial<Book> & { id: string; content?: string }) =>
|
|
41
|
+
apiFetch<Book>(`/api/books/${id}`, {
|
|
42
|
+
method: 'PUT',
|
|
43
|
+
headers: { 'Content-Type': 'application/json' },
|
|
44
|
+
body: JSON.stringify(data),
|
|
45
|
+
}),
|
|
46
|
+
onSuccess: (_, variables) => {
|
|
47
|
+
queryClient.invalidateQueries({ queryKey: ['books'] })
|
|
48
|
+
if (variables.id) queryClient.invalidateQueries({ queryKey: ['book', variables.id] })
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function useDeleteBook() {
|
|
54
|
+
const queryClient = useQueryClient()
|
|
55
|
+
return useMutation({
|
|
56
|
+
mutationFn: (id: string) =>
|
|
57
|
+
apiFetch(`/api/books/${id}`, { method: 'DELETE' }),
|
|
58
|
+
onSuccess: () => {
|
|
59
|
+
queryClient.invalidateQueries({ queryKey: ['books'] })
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
4
|
+
import { apiFetch } from '@/lib/apiFetch'
|
|
5
|
+
import type { CalendarEvent } from '@/types'
|
|
6
|
+
|
|
7
|
+
export function useCalendarEvents(weekStart?: string, weekEnd?: string) {
|
|
8
|
+
return useQuery({
|
|
9
|
+
queryKey: ['calendar', weekStart, weekEnd],
|
|
10
|
+
queryFn: () => {
|
|
11
|
+
const params = new URLSearchParams()
|
|
12
|
+
if (weekStart) params.set('week_start', weekStart)
|
|
13
|
+
if (weekEnd) params.set('week_end', weekEnd)
|
|
14
|
+
return apiFetch<CalendarEvent[]>(`/api/calendar?${params}`)
|
|
15
|
+
},
|
|
16
|
+
enabled: !!weekStart && !!weekEnd,
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useCreateCalendarEvent() {
|
|
21
|
+
const queryClient = useQueryClient()
|
|
22
|
+
return useMutation({
|
|
23
|
+
mutationFn: (data: Partial<CalendarEvent>) =>
|
|
24
|
+
apiFetch<CalendarEvent>('/api/calendar', {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'Content-Type': 'application/json' },
|
|
27
|
+
body: JSON.stringify(data),
|
|
28
|
+
}),
|
|
29
|
+
onSuccess: () => {
|
|
30
|
+
queryClient.invalidateQueries({ queryKey: ['calendar'] })
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useUpdateCalendarEvent() {
|
|
36
|
+
const queryClient = useQueryClient()
|
|
37
|
+
return useMutation({
|
|
38
|
+
mutationFn: ({ id, ...data }: Partial<CalendarEvent> & { id: string }) =>
|
|
39
|
+
apiFetch<CalendarEvent>(`/api/calendar/${id}`, {
|
|
40
|
+
method: 'PUT',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify(data),
|
|
43
|
+
}),
|
|
44
|
+
onSuccess: () => {
|
|
45
|
+
queryClient.invalidateQueries({ queryKey: ['calendar'] })
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function useDeleteCalendarEvent() {
|
|
51
|
+
const queryClient = useQueryClient()
|
|
52
|
+
return useMutation({
|
|
53
|
+
mutationFn: (id: string) =>
|
|
54
|
+
apiFetch(`/api/calendar/${id}`, { method: 'DELETE' }),
|
|
55
|
+
onSuccess: () => {
|
|
56
|
+
queryClient.invalidateQueries({ queryKey: ['calendar'] })
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useQuery } from '@tanstack/react-query'
|
|
4
|
+
import { apiFetch } from '@/lib/apiFetch'
|
|
5
|
+
import { getToday } from '@/lib/date'
|
|
6
|
+
import type { DailyManifest } from '@/types'
|
|
7
|
+
|
|
8
|
+
export function useDaily(date?: string) {
|
|
9
|
+
const today = date ?? getToday()
|
|
10
|
+
|
|
11
|
+
return useQuery({
|
|
12
|
+
queryKey: ['daily', today],
|
|
13
|
+
queryFn: () => apiFetch<DailyManifest>(`/api/daily?date=${today}`),
|
|
14
|
+
})
|
|
15
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { apiFetch } from '@/lib/apiFetch'
|
|
3
|
+
import type { Task } from '@/types'
|
|
4
|
+
|
|
5
|
+
export function useGlobalTasks(view?: string, sort?: string) {
|
|
6
|
+
return useQuery({
|
|
7
|
+
queryKey: ['tasks', 'global', view, sort],
|
|
8
|
+
queryFn: () => {
|
|
9
|
+
const params = new URLSearchParams()
|
|
10
|
+
if (view) params.set('view', view)
|
|
11
|
+
if (sort) params.set('sort', sort)
|
|
12
|
+
return apiFetch<Task[]>(`/api/tasks?${params}`)
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useQuickCreateTask() {
|
|
18
|
+
const queryClient = useQueryClient()
|
|
19
|
+
return useMutation({
|
|
20
|
+
mutationFn: (data: { title: string; project_id?: string; due_date?: string; priority?: string }) =>
|
|
21
|
+
apiFetch<Task>('/api/tasks', {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({ ...data, status: 'todo' }),
|
|
25
|
+
}),
|
|
26
|
+
onSuccess: () => {
|
|
27
|
+
queryClient.invalidateQueries({ queryKey: ['tasks'] })
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useToggleTaskDone() {
|
|
33
|
+
const queryClient = useQueryClient()
|
|
34
|
+
return useMutation({
|
|
35
|
+
mutationFn: ({ id, currentStatus }: { id: string; currentStatus: string }) =>
|
|
36
|
+
apiFetch<Task>(`/api/tasks/${id}`, {
|
|
37
|
+
method: 'PUT',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify({ status: currentStatus === 'done' ? 'todo' : 'done' }),
|
|
40
|
+
}),
|
|
41
|
+
onSuccess: () => {
|
|
42
|
+
queryClient.invalidateQueries({ queryKey: ['tasks'] })
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
4
|
+
import { apiFetch } from '@/lib/apiFetch'
|
|
5
|
+
import type { Goal } from '@/types'
|
|
6
|
+
|
|
7
|
+
export function useGoals() {
|
|
8
|
+
return useQuery({
|
|
9
|
+
queryKey: ['goals'],
|
|
10
|
+
queryFn: () => apiFetch<Goal[]>('/api/goals'),
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useCreateGoal() {
|
|
15
|
+
const queryClient = useQueryClient()
|
|
16
|
+
return useMutation({
|
|
17
|
+
mutationFn: (data: Partial<Goal>) =>
|
|
18
|
+
apiFetch<Goal>('/api/goals', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify(data),
|
|
22
|
+
}),
|
|
23
|
+
onSuccess: () => {
|
|
24
|
+
queryClient.invalidateQueries({ queryKey: ['goals'] })
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useUpdateGoal() {
|
|
30
|
+
const queryClient = useQueryClient()
|
|
31
|
+
return useMutation({
|
|
32
|
+
mutationFn: ({ id, ...data }: Partial<Goal> & { id: string }) =>
|
|
33
|
+
apiFetch<Goal>(`/api/goals/${id}`, {
|
|
34
|
+
method: 'PUT',
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
body: JSON.stringify(data),
|
|
37
|
+
}),
|
|
38
|
+
onSuccess: () => {
|
|
39
|
+
queryClient.invalidateQueries({ queryKey: ['goals'] })
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function useDeleteGoal() {
|
|
45
|
+
const queryClient = useQueryClient()
|
|
46
|
+
return useMutation({
|
|
47
|
+
mutationFn: (id: string) =>
|
|
48
|
+
apiFetch(`/api/goals/${id}`, { method: 'DELETE' }),
|
|
49
|
+
onSuccess: () => {
|
|
50
|
+
queryClient.invalidateQueries({ queryKey: ['goals'] })
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
5
|
+
import { apiFetch } from '@/lib/apiFetch'
|
|
6
|
+
import type { Milestone, MilestoneWithDDay } from '@/types'
|
|
7
|
+
|
|
8
|
+
export function useMilestones(goalId?: string) {
|
|
9
|
+
return useQuery({
|
|
10
|
+
queryKey: ['milestones', goalId],
|
|
11
|
+
queryFn: () => {
|
|
12
|
+
const url = goalId ? `/api/milestones?goal_id=${goalId}` : '/api/milestones'
|
|
13
|
+
return apiFetch<Milestone[]>(url)
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useMilestonesWithDDay(goalId?: string) {
|
|
19
|
+
const query = useMilestones(goalId)
|
|
20
|
+
|
|
21
|
+
const withDDay = useMemo(() =>
|
|
22
|
+
(query.data ?? [])
|
|
23
|
+
.map(m => ({
|
|
24
|
+
...m,
|
|
25
|
+
d_day: Math.ceil(
|
|
26
|
+
(new Date(m.due_date).getTime() - Date.now()) / (1000 * 60 * 60 * 24)
|
|
27
|
+
),
|
|
28
|
+
}))
|
|
29
|
+
.sort((a, b) => a.d_day - b.d_day),
|
|
30
|
+
[query.data]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return { ...query, data: withDDay }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useCreateMilestone() {
|
|
37
|
+
const queryClient = useQueryClient()
|
|
38
|
+
return useMutation({
|
|
39
|
+
mutationFn: (data: Partial<Milestone>) =>
|
|
40
|
+
apiFetch<Milestone>('/api/milestones', {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
body: JSON.stringify(data),
|
|
44
|
+
}),
|
|
45
|
+
onSuccess: () => {
|
|
46
|
+
queryClient.invalidateQueries({ queryKey: ['milestones'] })
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function useUpdateMilestone() {
|
|
52
|
+
const queryClient = useQueryClient()
|
|
53
|
+
return useMutation({
|
|
54
|
+
mutationFn: ({ id, ...data }: Partial<Milestone> & { id: string }) =>
|
|
55
|
+
apiFetch<Milestone>(`/api/milestones/${id}`, {
|
|
56
|
+
method: 'PUT',
|
|
57
|
+
headers: { 'Content-Type': 'application/json' },
|
|
58
|
+
body: JSON.stringify(data),
|
|
59
|
+
}),
|
|
60
|
+
onSuccess: () => {
|
|
61
|
+
queryClient.invalidateQueries({ queryKey: ['milestones'] })
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function useDeleteMilestone() {
|
|
67
|
+
const queryClient = useQueryClient()
|
|
68
|
+
return useMutation({
|
|
69
|
+
mutationFn: (id: string) =>
|
|
70
|
+
apiFetch(`/api/milestones/${id}`, { method: 'DELETE' }),
|
|
71
|
+
onSuccess: () => {
|
|
72
|
+
queryClient.invalidateQueries({ queryKey: ['milestones'] })
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { apiFetch } from '@/lib/apiFetch'
|
|
3
|
+
import type { Note, NoteType } from '@/types'
|
|
4
|
+
|
|
5
|
+
export function useNote(id: string) {
|
|
6
|
+
return useQuery({
|
|
7
|
+
queryKey: ['note', id],
|
|
8
|
+
queryFn: () => apiFetch<Note & { _content?: string }>(`/api/notes/${id}`),
|
|
9
|
+
enabled: !!id,
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useNotes(type?: NoteType, areaId?: string) {
|
|
14
|
+
return useQuery({
|
|
15
|
+
queryKey: ['notes', type, areaId],
|
|
16
|
+
queryFn: () => {
|
|
17
|
+
const params = new URLSearchParams()
|
|
18
|
+
if (type) params.set('type', type)
|
|
19
|
+
if (areaId) params.set('area_id', areaId)
|
|
20
|
+
return apiFetch<(Note & { _content?: string })[]>(`/api/notes?${params}`)
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useCreateNote() {
|
|
26
|
+
const queryClient = useQueryClient()
|
|
27
|
+
return useMutation({
|
|
28
|
+
mutationFn: (data: Partial<Note> & { content?: string }) =>
|
|
29
|
+
apiFetch<Note>('/api/notes', {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: { 'Content-Type': 'application/json' },
|
|
32
|
+
body: JSON.stringify(data),
|
|
33
|
+
}),
|
|
34
|
+
onSuccess: () => {
|
|
35
|
+
queryClient.invalidateQueries({ queryKey: ['notes'] })
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function useUpdateNote() {
|
|
41
|
+
const queryClient = useQueryClient()
|
|
42
|
+
return useMutation({
|
|
43
|
+
mutationFn: ({ id, ...data }: Partial<Note> & { id: string; content?: string }) =>
|
|
44
|
+
apiFetch<Note>(`/api/notes/${id}`, {
|
|
45
|
+
method: 'PUT',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify(data),
|
|
48
|
+
}),
|
|
49
|
+
onSuccess: (_, variables) => {
|
|
50
|
+
queryClient.invalidateQueries({ queryKey: ['notes'] })
|
|
51
|
+
if (variables.id) queryClient.invalidateQueries({ queryKey: ['note', variables.id] })
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useDeleteNote() {
|
|
57
|
+
const queryClient = useQueryClient()
|
|
58
|
+
return useMutation({
|
|
59
|
+
mutationFn: (id: string) =>
|
|
60
|
+
apiFetch(`/api/notes/${id}`, { method: 'DELETE' }),
|
|
61
|
+
onSuccess: () => {
|
|
62
|
+
queryClient.invalidateQueries({ queryKey: ['notes'] })
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
4
|
+
import { apiFetch } from '@/lib/apiFetch'
|
|
5
|
+
import type { Project, Task } from '@/types'
|
|
6
|
+
|
|
7
|
+
export function useProjects() {
|
|
8
|
+
return useQuery({
|
|
9
|
+
queryKey: ['projects'],
|
|
10
|
+
queryFn: () => apiFetch<Project[]>('/api/projects'),
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useTask(id: string) {
|
|
15
|
+
return useQuery({
|
|
16
|
+
queryKey: ['task', id],
|
|
17
|
+
queryFn: () => apiFetch<Task & { _content?: string }>(`/api/tasks/${id}`),
|
|
18
|
+
enabled: !!id,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useTasks(projectId?: string) {
|
|
23
|
+
return useQuery({
|
|
24
|
+
queryKey: ['tasks', projectId],
|
|
25
|
+
queryFn: () => {
|
|
26
|
+
const url = projectId ? `/api/tasks?project_id=${projectId}` : '/api/tasks'
|
|
27
|
+
return apiFetch<Task[]>(url)
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useCreateTask() {
|
|
33
|
+
const queryClient = useQueryClient()
|
|
34
|
+
return useMutation({
|
|
35
|
+
mutationFn: (data: Partial<Task>) =>
|
|
36
|
+
apiFetch<Task>('/api/tasks', {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify(data),
|
|
40
|
+
}),
|
|
41
|
+
onSuccess: (_, variables) => {
|
|
42
|
+
queryClient.invalidateQueries({ queryKey: ['tasks', variables.project_id] })
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useUpdateTask() {
|
|
48
|
+
const queryClient = useQueryClient()
|
|
49
|
+
return useMutation({
|
|
50
|
+
mutationFn: ({ id, ...data }: Partial<Task> & { id: string; content?: string }) =>
|
|
51
|
+
apiFetch<Task>(`/api/tasks/${id}`, {
|
|
52
|
+
method: 'PUT',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
body: JSON.stringify(data),
|
|
55
|
+
}),
|
|
56
|
+
onSuccess: (_, variables) => {
|
|
57
|
+
queryClient.invalidateQueries({ queryKey: ['tasks'] })
|
|
58
|
+
queryClient.invalidateQueries({ queryKey: ['task', variables.id] })
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function useDeleteTask() {
|
|
64
|
+
const queryClient = useQueryClient()
|
|
65
|
+
return useMutation({
|
|
66
|
+
mutationFn: (id: string) =>
|
|
67
|
+
apiFetch(`/api/tasks/${id}`, { method: 'DELETE' }),
|
|
68
|
+
onSuccess: () => {
|
|
69
|
+
queryClient.invalidateQueries({ queryKey: ['tasks'] })
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function useCreateProject() {
|
|
75
|
+
const queryClient = useQueryClient()
|
|
76
|
+
return useMutation({
|
|
77
|
+
mutationFn: (data: Partial<Project>) =>
|
|
78
|
+
apiFetch<Project>('/api/projects', {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: { 'Content-Type': 'application/json' },
|
|
81
|
+
body: JSON.stringify(data),
|
|
82
|
+
}),
|
|
83
|
+
onSuccess: () => {
|
|
84
|
+
queryClient.invalidateQueries({ queryKey: ['projects'] })
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function useUpdateProject() {
|
|
90
|
+
const queryClient = useQueryClient()
|
|
91
|
+
return useMutation({
|
|
92
|
+
mutationFn: ({ id, ...data }: Partial<Project> & { id: string }) =>
|
|
93
|
+
apiFetch<Project>(`/api/projects/${id}`, {
|
|
94
|
+
method: 'PUT',
|
|
95
|
+
headers: { 'Content-Type': 'application/json' },
|
|
96
|
+
body: JSON.stringify(data),
|
|
97
|
+
}),
|
|
98
|
+
onSuccess: () => {
|
|
99
|
+
queryClient.invalidateQueries({ queryKey: ['projects'] })
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
5
|
+
import { apiFetch } from '@/lib/apiFetch'
|
|
6
|
+
import { getToday } from '@/lib/date'
|
|
7
|
+
import { buildHabitChain } from '@/lib/neuroscience/habitStack'
|
|
8
|
+
import type { Routine, RoutineLog, RoutineWithStatus } from '@/types'
|
|
9
|
+
|
|
10
|
+
export function useRoutines() {
|
|
11
|
+
const today = getToday()
|
|
12
|
+
|
|
13
|
+
const routinesQuery = useQuery({
|
|
14
|
+
queryKey: ['routines'],
|
|
15
|
+
queryFn: () => apiFetch<Routine[]>('/api/routines'),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const logsQuery = useQuery({
|
|
19
|
+
queryKey: ['routine-logs', today],
|
|
20
|
+
queryFn: () => apiFetch<RoutineLog>(`/api/logs/routine?date=${today}`),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const routinesWithStatus = useMemo<RoutineWithStatus[]>(() =>
|
|
24
|
+
(routinesQuery.data ?? [])
|
|
25
|
+
.filter(r => r.is_active)
|
|
26
|
+
.map(r => ({
|
|
27
|
+
...r,
|
|
28
|
+
completed_today: logsQuery.data?.completions?.some(c => c.routine_id === r.id) ?? false,
|
|
29
|
+
})),
|
|
30
|
+
[routinesQuery.data, logsQuery.data]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const chains = useMemo(() => buildHabitChain(routinesWithStatus), [routinesWithStatus])
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
chains,
|
|
37
|
+
routines: routinesWithStatus,
|
|
38
|
+
isLoading: routinesQuery.isLoading || logsQuery.isLoading,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useToggleRoutine() {
|
|
43
|
+
const queryClient = useQueryClient()
|
|
44
|
+
const today = getToday()
|
|
45
|
+
|
|
46
|
+
return useMutation({
|
|
47
|
+
mutationFn: ({ routineId, completed }: { routineId: string; completed: boolean }) =>
|
|
48
|
+
apiFetch('/api/logs/routine', {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: { 'Content-Type': 'application/json' },
|
|
51
|
+
body: JSON.stringify({ routine_id: routineId, undo: completed, date: today }),
|
|
52
|
+
}),
|
|
53
|
+
onMutate: async ({ routineId, completed }) => {
|
|
54
|
+
await queryClient.cancelQueries({ queryKey: ['routine-logs', today] })
|
|
55
|
+
const prev = queryClient.getQueryData<RoutineLog>(['routine-logs', today])
|
|
56
|
+
|
|
57
|
+
queryClient.setQueryData<RoutineLog>(['routine-logs', today], (old) => {
|
|
58
|
+
if (!old) return { date: today, completions: completed ? [] : [{ routine_id: routineId, completed_at: new Date().toISOString() }] }
|
|
59
|
+
return {
|
|
60
|
+
...old,
|
|
61
|
+
completions: completed
|
|
62
|
+
? old.completions.filter(c => c.routine_id !== routineId)
|
|
63
|
+
: [...old.completions, { routine_id: routineId, completed_at: new Date().toISOString() }],
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return { prev }
|
|
68
|
+
},
|
|
69
|
+
onError: (_, __, ctx) => {
|
|
70
|
+
if (ctx?.prev) queryClient.setQueryData(['routine-logs', today], ctx.prev)
|
|
71
|
+
},
|
|
72
|
+
onSettled: () => {
|
|
73
|
+
queryClient.invalidateQueries({ queryKey: ['routine-logs', today] })
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
}
|