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.
Files changed (180) hide show
  1. package/README.md +77 -0
  2. package/bin/arete.js +156 -0
  3. package/bin/create.js +111 -0
  4. package/lib/install-openclaw.js +50 -0
  5. package/lib/scaffold.js +213 -0
  6. package/lib/setup-wizard.js +88 -0
  7. package/lib/updater.js +130 -0
  8. package/package.json +34 -0
  9. package/packages/gatsaeng-os/README.md +36 -0
  10. package/packages/gatsaeng-os/components.json +23 -0
  11. package/packages/gatsaeng-os/eslint.config.mjs +18 -0
  12. package/packages/gatsaeng-os/next.config.ts +7 -0
  13. package/packages/gatsaeng-os/package.json +59 -0
  14. package/packages/gatsaeng-os/postcss.config.mjs +7 -0
  15. package/packages/gatsaeng-os/public/file.svg +1 -0
  16. package/packages/gatsaeng-os/public/globe.svg +1 -0
  17. package/packages/gatsaeng-os/public/next.svg +1 -0
  18. package/packages/gatsaeng-os/public/vercel.svg +1 -0
  19. package/packages/gatsaeng-os/public/window.svg +1 -0
  20. package/packages/gatsaeng-os/python/api_server.py +248 -0
  21. package/packages/gatsaeng-os/python/briefing.py +145 -0
  22. package/packages/gatsaeng-os/python/config.py +55 -0
  23. package/packages/gatsaeng-os/python/goal_context_agent.py +193 -0
  24. package/packages/gatsaeng-os/python/gyeokguk.py +171 -0
  25. package/packages/gatsaeng-os/python/proactive.py +158 -0
  26. package/packages/gatsaeng-os/python/requirements.txt +11 -0
  27. package/packages/gatsaeng-os/python/run.py +28 -0
  28. package/packages/gatsaeng-os/python/scoring.py +44 -0
  29. package/packages/gatsaeng-os/python/streak.py +70 -0
  30. package/packages/gatsaeng-os/python/telegram_bot.py +331 -0
  31. package/packages/gatsaeng-os/python/timing_engine.py +117 -0
  32. package/packages/gatsaeng-os/python/vault_io.py +423 -0
  33. package/packages/gatsaeng-os/src/app/(dashboard)/areas/[id]/page.tsx +215 -0
  34. package/packages/gatsaeng-os/src/app/(dashboard)/areas/page.tsx +161 -0
  35. package/packages/gatsaeng-os/src/app/(dashboard)/books/[id]/page.tsx +215 -0
  36. package/packages/gatsaeng-os/src/app/(dashboard)/books/page.tsx +268 -0
  37. package/packages/gatsaeng-os/src/app/(dashboard)/calendar/page.tsx +379 -0
  38. package/packages/gatsaeng-os/src/app/(dashboard)/error.tsx +30 -0
  39. package/packages/gatsaeng-os/src/app/(dashboard)/focus/page.tsx +293 -0
  40. package/packages/gatsaeng-os/src/app/(dashboard)/goals/[id]/page.tsx +426 -0
  41. package/packages/gatsaeng-os/src/app/(dashboard)/goals/page.tsx +178 -0
  42. package/packages/gatsaeng-os/src/app/(dashboard)/layout.tsx +29 -0
  43. package/packages/gatsaeng-os/src/app/(dashboard)/notes/[id]/page.tsx +147 -0
  44. package/packages/gatsaeng-os/src/app/(dashboard)/notes/page.tsx +254 -0
  45. package/packages/gatsaeng-os/src/app/(dashboard)/page.tsx +26 -0
  46. package/packages/gatsaeng-os/src/app/(dashboard)/projects/[id]/page.tsx +86 -0
  47. package/packages/gatsaeng-os/src/app/(dashboard)/projects/page.tsx +215 -0
  48. package/packages/gatsaeng-os/src/app/(dashboard)/review/page.tsx +475 -0
  49. package/packages/gatsaeng-os/src/app/(dashboard)/routines/page.tsx +436 -0
  50. package/packages/gatsaeng-os/src/app/(dashboard)/tasks/[id]/page.tsx +210 -0
  51. package/packages/gatsaeng-os/src/app/(dashboard)/tasks/page.tsx +307 -0
  52. package/packages/gatsaeng-os/src/app/(dashboard)/voice/page.tsx +212 -0
  53. package/packages/gatsaeng-os/src/app/api/areas/[id]/route.ts +26 -0
  54. package/packages/gatsaeng-os/src/app/api/areas/route.ts +22 -0
  55. package/packages/gatsaeng-os/src/app/api/auth/login/route.ts +52 -0
  56. package/packages/gatsaeng-os/src/app/api/auth/logout/route.ts +8 -0
  57. package/packages/gatsaeng-os/src/app/api/books/[id]/route.ts +27 -0
  58. package/packages/gatsaeng-os/src/app/api/books/route.ts +20 -0
  59. package/packages/gatsaeng-os/src/app/api/calendar/[id]/route.ts +24 -0
  60. package/packages/gatsaeng-os/src/app/api/calendar/import/route.ts +52 -0
  61. package/packages/gatsaeng-os/src/app/api/calendar/route.ts +37 -0
  62. package/packages/gatsaeng-os/src/app/api/daily/route.ts +51 -0
  63. package/packages/gatsaeng-os/src/app/api/goals/[id]/route.ts +34 -0
  64. package/packages/gatsaeng-os/src/app/api/goals/route.ts +30 -0
  65. package/packages/gatsaeng-os/src/app/api/logs/energy/route.ts +40 -0
  66. package/packages/gatsaeng-os/src/app/api/logs/focus/route.ts +22 -0
  67. package/packages/gatsaeng-os/src/app/api/logs/routine/route.ts +54 -0
  68. package/packages/gatsaeng-os/src/app/api/milestones/[id]/route.ts +26 -0
  69. package/packages/gatsaeng-os/src/app/api/milestones/route.ts +47 -0
  70. package/packages/gatsaeng-os/src/app/api/notes/[id]/route.ts +29 -0
  71. package/packages/gatsaeng-os/src/app/api/notes/route.ts +37 -0
  72. package/packages/gatsaeng-os/src/app/api/profile/route.ts +17 -0
  73. package/packages/gatsaeng-os/src/app/api/projects/[id]/route.ts +27 -0
  74. package/packages/gatsaeng-os/src/app/api/projects/route.ts +25 -0
  75. package/packages/gatsaeng-os/src/app/api/reviews/[id]/route.ts +26 -0
  76. package/packages/gatsaeng-os/src/app/api/reviews/route.ts +29 -0
  77. package/packages/gatsaeng-os/src/app/api/routines/[id]/route.ts +26 -0
  78. package/packages/gatsaeng-os/src/app/api/routines/route.ts +28 -0
  79. package/packages/gatsaeng-os/src/app/api/tasks/[id]/route.ts +28 -0
  80. package/packages/gatsaeng-os/src/app/api/tasks/route.ts +66 -0
  81. package/packages/gatsaeng-os/src/app/api/timing/current/route.ts +63 -0
  82. package/packages/gatsaeng-os/src/app/api/voice/chat/route.ts +50 -0
  83. package/packages/gatsaeng-os/src/app/api/voice/transcribe/route.ts +25 -0
  84. package/packages/gatsaeng-os/src/app/api/voice/tts/route.ts +36 -0
  85. package/packages/gatsaeng-os/src/app/error.tsx +30 -0
  86. package/packages/gatsaeng-os/src/app/favicon.ico +0 -0
  87. package/packages/gatsaeng-os/src/app/globals.css +208 -0
  88. package/packages/gatsaeng-os/src/app/layout.tsx +33 -0
  89. package/packages/gatsaeng-os/src/app/login/page.tsx +87 -0
  90. package/packages/gatsaeng-os/src/app/providers.tsx +27 -0
  91. package/packages/gatsaeng-os/src/components/ErrorBoundary.tsx +46 -0
  92. package/packages/gatsaeng-os/src/components/dashboard/DashboardGrid.tsx +86 -0
  93. package/packages/gatsaeng-os/src/components/dashboard/DdayWidget.tsx +88 -0
  94. package/packages/gatsaeng-os/src/components/dashboard/EnergyTracker.tsx +87 -0
  95. package/packages/gatsaeng-os/src/components/dashboard/FocusTimer.tsx +139 -0
  96. package/packages/gatsaeng-os/src/components/dashboard/GatsaengScore.tsx +30 -0
  97. package/packages/gatsaeng-os/src/components/dashboard/GoalRings.tsx +107 -0
  98. package/packages/gatsaeng-os/src/components/dashboard/ProactiveBar.tsx +98 -0
  99. package/packages/gatsaeng-os/src/components/dashboard/RoutineChecklist.tsx +81 -0
  100. package/packages/gatsaeng-os/src/components/dashboard/TimingWidget.tsx +86 -0
  101. package/packages/gatsaeng-os/src/components/dashboard/WidgetCustomizer.tsx +95 -0
  102. package/packages/gatsaeng-os/src/components/dashboard/WidgetWrapper.tsx +33 -0
  103. package/packages/gatsaeng-os/src/components/dashboard/ZeigarnikPanel.tsx +43 -0
  104. package/packages/gatsaeng-os/src/components/editor/EditorToolbar.tsx +186 -0
  105. package/packages/gatsaeng-os/src/components/editor/TiptapEditor.tsx +114 -0
  106. package/packages/gatsaeng-os/src/components/layout/Header.tsx +47 -0
  107. package/packages/gatsaeng-os/src/components/layout/MobileBottomNav.tsx +122 -0
  108. package/packages/gatsaeng-os/src/components/layout/MobileSidebar.tsx +29 -0
  109. package/packages/gatsaeng-os/src/components/layout/Sidebar.tsx +142 -0
  110. package/packages/gatsaeng-os/src/components/onboarding/OnboardingFlow.tsx +229 -0
  111. package/packages/gatsaeng-os/src/components/onboarding/OnboardingGate.tsx +78 -0
  112. package/packages/gatsaeng-os/src/components/projects/CalendarView.tsx +152 -0
  113. package/packages/gatsaeng-os/src/components/projects/KanbanView.tsx +180 -0
  114. package/packages/gatsaeng-os/src/components/projects/ListView.tsx +82 -0
  115. package/packages/gatsaeng-os/src/components/projects/TableView.tsx +206 -0
  116. package/packages/gatsaeng-os/src/components/projects/TaskCard.tsx +154 -0
  117. package/packages/gatsaeng-os/src/components/projects/TaskForm.tsx +128 -0
  118. package/packages/gatsaeng-os/src/components/projects/ViewSwitcher.tsx +40 -0
  119. package/packages/gatsaeng-os/src/components/search/GlobalSearch.tsx +179 -0
  120. package/packages/gatsaeng-os/src/components/shared/InlineEdit.tsx +77 -0
  121. package/packages/gatsaeng-os/src/components/shared/PinButton.tsx +42 -0
  122. package/packages/gatsaeng-os/src/components/tasks/DDayBadge.tsx +34 -0
  123. package/packages/gatsaeng-os/src/components/ui/badge.tsx +48 -0
  124. package/packages/gatsaeng-os/src/components/ui/button.tsx +64 -0
  125. package/packages/gatsaeng-os/src/components/ui/card.tsx +92 -0
  126. package/packages/gatsaeng-os/src/components/ui/checkbox.tsx +32 -0
  127. package/packages/gatsaeng-os/src/components/ui/command.tsx +184 -0
  128. package/packages/gatsaeng-os/src/components/ui/dialog.tsx +158 -0
  129. package/packages/gatsaeng-os/src/components/ui/input.tsx +21 -0
  130. package/packages/gatsaeng-os/src/components/ui/label.tsx +24 -0
  131. package/packages/gatsaeng-os/src/components/ui/popover.tsx +89 -0
  132. package/packages/gatsaeng-os/src/components/ui/progress.tsx +31 -0
  133. package/packages/gatsaeng-os/src/components/ui/select.tsx +190 -0
  134. package/packages/gatsaeng-os/src/components/ui/sheet.tsx +143 -0
  135. package/packages/gatsaeng-os/src/components/ui/tabs.tsx +91 -0
  136. package/packages/gatsaeng-os/src/components/ui/toggle-group.tsx +83 -0
  137. package/packages/gatsaeng-os/src/components/ui/toggle.tsx +47 -0
  138. package/packages/gatsaeng-os/src/components/ui/tooltip.tsx +57 -0
  139. package/packages/gatsaeng-os/src/hooks/useAreas.ts +53 -0
  140. package/packages/gatsaeng-os/src/hooks/useBooks.ts +62 -0
  141. package/packages/gatsaeng-os/src/hooks/useCalendar.ts +59 -0
  142. package/packages/gatsaeng-os/src/hooks/useDaily.ts +15 -0
  143. package/packages/gatsaeng-os/src/hooks/useGlobalTasks.ts +45 -0
  144. package/packages/gatsaeng-os/src/hooks/useGoals.ts +53 -0
  145. package/packages/gatsaeng-os/src/hooks/useMilestones.ts +75 -0
  146. package/packages/gatsaeng-os/src/hooks/useNotes.ts +65 -0
  147. package/packages/gatsaeng-os/src/hooks/useProjects.ts +102 -0
  148. package/packages/gatsaeng-os/src/hooks/useRoutines.ts +76 -0
  149. package/packages/gatsaeng-os/src/hooks/useTiming.ts +27 -0
  150. package/packages/gatsaeng-os/src/lib/apiFetch.ts +14 -0
  151. package/packages/gatsaeng-os/src/lib/auth.ts +32 -0
  152. package/packages/gatsaeng-os/src/lib/date.ts +7 -0
  153. package/packages/gatsaeng-os/src/lib/editor/markdown.ts +35 -0
  154. package/packages/gatsaeng-os/src/lib/llm-governor.ts +167 -0
  155. package/packages/gatsaeng-os/src/lib/neuroscience/energyCycle.ts +35 -0
  156. package/packages/gatsaeng-os/src/lib/neuroscience/habitStack.ts +22 -0
  157. package/packages/gatsaeng-os/src/lib/neuroscience/scoring.ts +32 -0
  158. package/packages/gatsaeng-os/src/lib/routes.ts +15 -0
  159. package/packages/gatsaeng-os/src/lib/utils.ts +6 -0
  160. package/packages/gatsaeng-os/src/lib/vault/config.ts +29 -0
  161. package/packages/gatsaeng-os/src/lib/vault/frontmatter.ts +84 -0
  162. package/packages/gatsaeng-os/src/lib/vault/index.ts +180 -0
  163. package/packages/gatsaeng-os/src/lib/vault/schemas.ts +274 -0
  164. package/packages/gatsaeng-os/src/middleware.ts +34 -0
  165. package/packages/gatsaeng-os/src/stores/dashboardStore.ts +26 -0
  166. package/packages/gatsaeng-os/src/stores/favoritesStore.ts +47 -0
  167. package/packages/gatsaeng-os/src/stores/timerStore.ts +65 -0
  168. package/packages/gatsaeng-os/src/types/index.ts +320 -0
  169. package/packages/gatsaeng-os/tsconfig.json +34 -0
  170. package/templates/scripts/forge_qa.sh.tmpl +237 -0
  171. package/templates/scripts/forge_ship.sh.tmpl +183 -0
  172. package/templates/scripts/session_indexer.py.tmpl +420 -0
  173. package/templates/scripts/tracer.py.tmpl +266 -0
  174. package/templates/workspace/AGENTS.md.tmpl +190 -0
  175. package/templates/workspace/BOOTSTRAP.md.tmpl +27 -0
  176. package/templates/workspace/HEARTBEAT.md.tmpl +23 -0
  177. package/templates/workspace/MEMORY.md.tmpl +35 -0
  178. package/templates/workspace/SOUL.md.tmpl +258 -0
  179. package/templates/workspace/TOOLS.md.tmpl +28 -0
  180. package/templates/workspace/USER.md.tmpl +43 -0
@@ -0,0 +1,154 @@
1
+ 'use client'
2
+
3
+ import { Card, CardContent } from '@/components/ui/card'
4
+ import { Badge } from '@/components/ui/badge'
5
+ import { GripVertical, Calendar as CalendarIcon, Sparkles, Check } from 'lucide-react'
6
+ import type { Task } from '@/types'
7
+ import { forwardRef, useEffect, useRef, useState } from 'react'
8
+
9
+ const LS_KEY = 'mc_delegated_task_ids'
10
+
11
+ function loadDelegated(): Set<string> {
12
+ try {
13
+ return new Set(JSON.parse(localStorage.getItem(LS_KEY) ?? '[]'))
14
+ } catch {
15
+ return new Set()
16
+ }
17
+ }
18
+
19
+ function markDelegated(taskId: string) {
20
+ try {
21
+ const s = loadDelegated()
22
+ s.add(taskId)
23
+ localStorage.setItem(LS_KEY, JSON.stringify([...s]))
24
+ } catch { /* ignore */ }
25
+ }
26
+
27
+ const PRIORITY_COLORS: Record<string, string> = {
28
+ urgent: 'border-gatsaeng-red text-gatsaeng-red',
29
+ high: 'border-gatsaeng-amber text-gatsaeng-amber',
30
+ medium: 'border-primary text-primary',
31
+ low: 'border-muted-foreground text-muted-foreground',
32
+ }
33
+
34
+ const ENERGY_LABELS: Record<string, string> = {
35
+ high: '⚡ High',
36
+ medium: '🔋 Med',
37
+ low: '🪫 Low',
38
+ }
39
+
40
+ interface TaskCardProps {
41
+ task: Task
42
+ onClick?: () => void
43
+ isDragging?: boolean
44
+ dragHandleProps?: Record<string, unknown>
45
+ style?: React.CSSProperties
46
+ }
47
+
48
+ export const TaskCard = forwardRef<HTMLDivElement, TaskCardProps>(
49
+ function TaskCard({ task, onClick, isDragging, dragHandleProps, style }, ref) {
50
+ const [delegateState, setDelegateState] = useState<'idle' | 'loading' | 'done' | 'error'>(
51
+ () => typeof window !== 'undefined' && loadDelegated().has(task.id) ? 'done' : 'idle'
52
+ )
53
+ const errorTimerRef = useRef<ReturnType<typeof setTimeout>>(null)
54
+
55
+ useEffect(() => {
56
+ return () => { if (errorTimerRef.current) clearTimeout(errorTimerRef.current) }
57
+ }, [])
58
+
59
+ async function handleDelegate(e: React.MouseEvent) {
60
+ e.stopPropagation()
61
+ if (delegateState === 'done' || delegateState === 'loading') return
62
+ setDelegateState('loading')
63
+ try {
64
+ const res = await fetch('/api/delegate-to-mc', {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json' },
67
+ body: JSON.stringify({
68
+ taskId: task.id,
69
+ title: task.title,
70
+ description: task.description ?? '',
71
+ priority: task.priority,
72
+ }),
73
+ })
74
+ if (!res.ok) throw new Error('delegate failed')
75
+ markDelegated(task.id)
76
+ setDelegateState('done')
77
+ } catch {
78
+ setDelegateState('error')
79
+ errorTimerRef.current = setTimeout(() => setDelegateState('idle'), 1500)
80
+ }
81
+ }
82
+
83
+ return (
84
+ <div ref={ref} style={style}>
85
+ <Card
86
+ className={`transition-colors cursor-pointer ${
87
+ isDragging ? 'opacity-50 border-primary' : 'hover:border-primary/30'
88
+ }`}
89
+ onClick={onClick}
90
+ >
91
+ <CardContent className="py-3 px-3">
92
+ <div className="flex items-start gap-2">
93
+ {dragHandleProps && (
94
+ <button {...dragHandleProps} className="mt-0.5 text-muted-foreground hover:text-foreground cursor-grab active:cursor-grabbing">
95
+ <GripVertical className="w-3.5 h-3.5" />
96
+ </button>
97
+ )}
98
+ <div className="flex-1 min-w-0">
99
+ <div className="text-sm text-foreground">{task.title}</div>
100
+ {task.description && (
101
+ <p className="text-xs text-muted-foreground mt-1 line-clamp-2">{task.description}</p>
102
+ )}
103
+ <div className="flex flex-wrap gap-1.5 mt-2">
104
+ <Badge variant="outline" className={`text-[10px] px-1.5 py-0 ${PRIORITY_COLORS[task.priority]}`}>
105
+ {task.priority}
106
+ </Badge>
107
+ {task.energy_required && (
108
+ <Badge variant="outline" className="text-[10px] px-1.5 py-0">
109
+ {ENERGY_LABELS[task.energy_required]}
110
+ </Badge>
111
+ )}
112
+ {task.tag && (
113
+ <Badge variant="outline" className="text-[10px] px-1.5 py-0">
114
+ {task.tag}
115
+ </Badge>
116
+ )}
117
+ </div>
118
+ {task.due_date && (
119
+ <div className="flex items-center gap-1 mt-1.5 text-[10px] text-muted-foreground">
120
+ <CalendarIcon className="w-3 h-3" />
121
+ {task.due_date.slice(0, 10)}
122
+ </div>
123
+ )}
124
+ <div className="flex justify-end mt-1.5">
125
+ <button
126
+ onClick={handleDelegate}
127
+ disabled={delegateState === 'done' || delegateState === 'loading'}
128
+ className={`flex items-center gap-0.5 text-[10px] transition-colors ${
129
+ delegateState === 'done'
130
+ ? 'text-green-500'
131
+ : delegateState === 'error'
132
+ ? 'text-gatsaeng-red'
133
+ : 'text-muted-foreground hover:text-gatsaeng-purple'
134
+ } disabled:cursor-default`}
135
+ >
136
+ {delegateState === 'done' ? (
137
+ <><Check className="w-3 h-3" /> MC 위임됨</>
138
+ ) : delegateState === 'error' ? (
139
+ '위임 실패'
140
+ ) : delegateState === 'loading' ? (
141
+ <><Sparkles className="w-3 h-3 animate-pulse" /> 위임 중…</>
142
+ ) : (
143
+ <><Sparkles className="w-3 h-3" /> Eve 위임</>
144
+ )}
145
+ </button>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </CardContent>
150
+ </Card>
151
+ </div>
152
+ )
153
+ }
154
+ )
@@ -0,0 +1,128 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
5
+ import { Button } from '@/components/ui/button'
6
+ import { Input } from '@/components/ui/input'
7
+ import { Label } from '@/components/ui/label'
8
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
9
+ import { Plus } from 'lucide-react'
10
+ import { useGoals } from '@/hooks/useGoals'
11
+ import type { Task, TaskStatus, TaskPriority, EnergyLevel } from '@/types'
12
+
13
+ interface TaskFormProps {
14
+ projectId: string
15
+ defaultStatus?: TaskStatus
16
+ onSubmit: (data: Partial<Task>) => Promise<void>
17
+ }
18
+
19
+ export function TaskForm({ projectId, defaultStatus = 'backlog', onSubmit }: TaskFormProps) {
20
+ const [open, setOpen] = useState(false)
21
+ const { data: goals } = useGoals()
22
+
23
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
24
+ e.preventDefault()
25
+ const form = new FormData(e.currentTarget)
26
+ const goalIds = form.get('goal_id') ? [form.get('goal_id') as string] : undefined
27
+ await onSubmit({
28
+ project_id: projectId,
29
+ title: form.get('title') as string,
30
+ description: (form.get('description') as string) || undefined,
31
+ status: (form.get('status') as TaskStatus) || defaultStatus,
32
+ priority: (form.get('priority') as TaskPriority) || 'medium',
33
+ energy_required: (form.get('energy_required') as EnergyLevel) || undefined,
34
+ tag: (form.get('tag') as string) || undefined,
35
+ due_date: (form.get('due_date') as string) || undefined,
36
+ goal_ids: goalIds,
37
+ })
38
+ setOpen(false)
39
+ }
40
+
41
+ return (
42
+ <Dialog open={open} onOpenChange={setOpen}>
43
+ <DialogTrigger asChild>
44
+ <Button size="sm" className="bg-gatsaeng-amber hover:bg-gatsaeng-amber/80 text-black">
45
+ <Plus className="w-4 h-4 mr-1" /> 태스크 추가
46
+ </Button>
47
+ </DialogTrigger>
48
+ <DialogContent className="max-w-lg">
49
+ <DialogHeader>
50
+ <DialogTitle>태스크 만들기</DialogTitle>
51
+ </DialogHeader>
52
+ <form onSubmit={handleSubmit} className="space-y-4">
53
+ <div>
54
+ <Label>제목</Label>
55
+ <Input name="title" required placeholder="태스크 제목" />
56
+ </div>
57
+ <div>
58
+ <Label>설명</Label>
59
+ <Input name="description" placeholder="태스크 설명 (선택)" />
60
+ </div>
61
+ <div className="grid grid-cols-2 gap-4">
62
+ <div>
63
+ <Label>상태</Label>
64
+ <Select name="status" defaultValue={defaultStatus}>
65
+ <SelectTrigger><SelectValue /></SelectTrigger>
66
+ <SelectContent>
67
+ <SelectItem value="backlog">Backlog</SelectItem>
68
+ <SelectItem value="todo">To Do</SelectItem>
69
+ <SelectItem value="doing">Doing</SelectItem>
70
+ <SelectItem value="done">Done</SelectItem>
71
+ </SelectContent>
72
+ </Select>
73
+ </div>
74
+ <div>
75
+ <Label>우선순위</Label>
76
+ <Select name="priority" defaultValue="medium">
77
+ <SelectTrigger><SelectValue /></SelectTrigger>
78
+ <SelectContent>
79
+ <SelectItem value="urgent">긴급</SelectItem>
80
+ <SelectItem value="high">높음</SelectItem>
81
+ <SelectItem value="medium">보통</SelectItem>
82
+ <SelectItem value="low">낮음</SelectItem>
83
+ </SelectContent>
84
+ </Select>
85
+ </div>
86
+ </div>
87
+ <div className="grid grid-cols-2 gap-4">
88
+ <div>
89
+ <Label>에너지 요구</Label>
90
+ <Select name="energy_required">
91
+ <SelectTrigger><SelectValue placeholder="선택" /></SelectTrigger>
92
+ <SelectContent>
93
+ <SelectItem value="high">높음 ⚡</SelectItem>
94
+ <SelectItem value="medium">보통 🔋</SelectItem>
95
+ <SelectItem value="low">낮음 🪫</SelectItem>
96
+ </SelectContent>
97
+ </Select>
98
+ </div>
99
+ <div>
100
+ <Label>마감일</Label>
101
+ <Input name="due_date" type="date" />
102
+ </div>
103
+ </div>
104
+ <div>
105
+ <Label>태그</Label>
106
+ <Input name="tag" placeholder="예: frontend, design, bug" />
107
+ </div>
108
+ {(goals ?? []).length > 0 && (
109
+ <div>
110
+ <Label>연결 목표</Label>
111
+ <Select name="goal_id">
112
+ <SelectTrigger><SelectValue placeholder="목표 선택 (선택)" /></SelectTrigger>
113
+ <SelectContent>
114
+ {(goals ?? []).map(goal => (
115
+ <SelectItem key={goal.id} value={goal.id}>{goal.title}</SelectItem>
116
+ ))}
117
+ </SelectContent>
118
+ </Select>
119
+ </div>
120
+ )}
121
+ <Button type="submit" className="w-full bg-gatsaeng-amber hover:bg-gatsaeng-amber/80 text-black">
122
+ 태스크 생성
123
+ </Button>
124
+ </form>
125
+ </DialogContent>
126
+ </Dialog>
127
+ )
128
+ }
@@ -0,0 +1,40 @@
1
+ 'use client'
2
+
3
+ import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
4
+ import { LayoutGrid, Table, Calendar, List } from 'lucide-react'
5
+ import type { ViewType } from '@/types'
6
+
7
+ const VIEWS: { value: ViewType; icon: React.ReactNode; label: string }[] = [
8
+ { value: 'kanban', icon: <LayoutGrid className="w-4 h-4" />, label: 'Kanban' },
9
+ { value: 'table', icon: <Table className="w-4 h-4" />, label: 'Table' },
10
+ { value: 'list', icon: <List className="w-4 h-4" />, label: 'List' },
11
+ { value: 'calendar', icon: <Calendar className="w-4 h-4" />, label: 'Calendar' },
12
+ ]
13
+
14
+ interface ViewSwitcherProps {
15
+ value: ViewType
16
+ onChange: (view: ViewType) => void
17
+ }
18
+
19
+ export function ViewSwitcher({ value, onChange }: ViewSwitcherProps) {
20
+ return (
21
+ <ToggleGroup
22
+ type="single"
23
+ value={value}
24
+ onValueChange={(v) => v && onChange(v as ViewType)}
25
+ className="bg-secondary/50 p-0.5 rounded-md"
26
+ >
27
+ {VIEWS.map(view => (
28
+ <ToggleGroupItem
29
+ key={view.value}
30
+ value={view.value}
31
+ aria-label={view.label}
32
+ className="px-2.5 py-1.5 text-xs gap-1.5 data-[state=on]:bg-card data-[state=on]:text-foreground"
33
+ >
34
+ {view.icon}
35
+ <span className="hidden sm:inline">{view.label}</span>
36
+ </ToggleGroupItem>
37
+ ))}
38
+ </ToggleGroup>
39
+ )
40
+ }
@@ -0,0 +1,179 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState, useMemo } from 'react'
4
+ import { useRouter } from 'next/navigation'
5
+ import {
6
+ CommandDialog,
7
+ CommandInput,
8
+ CommandList,
9
+ CommandEmpty,
10
+ CommandGroup,
11
+ CommandItem,
12
+ } from '@/components/ui/command'
13
+ import {
14
+ StickyNote,
15
+ CheckSquare,
16
+ Target,
17
+ FolderKanban,
18
+ BookMarked,
19
+ LayoutDashboard,
20
+ Layers,
21
+ RotateCcw,
22
+ CalendarDays,
23
+ Timer,
24
+ BookOpen,
25
+ } from 'lucide-react'
26
+ import { useNotes } from '@/hooks/useNotes'
27
+ import { useTasks } from '@/hooks/useProjects'
28
+ import { useGoals } from '@/hooks/useGoals'
29
+ import { useProjects } from '@/hooks/useProjects'
30
+ import { useBooks } from '@/hooks/useBooks'
31
+
32
+ const PAGES = [
33
+ { href: '/', label: '대시보드', icon: LayoutDashboard },
34
+ { href: '/areas', label: '영역', icon: Layers },
35
+ { href: '/goals', label: '목표', icon: Target },
36
+ { href: '/projects', label: '프로젝트', icon: FolderKanban },
37
+ { href: '/tasks', label: '할일', icon: CheckSquare },
38
+ { href: '/routines', label: '루틴', icon: RotateCcw },
39
+ { href: '/notes', label: '노트', icon: StickyNote },
40
+ { href: '/books', label: '독서', icon: BookMarked },
41
+ { href: '/calendar', label: '캘린더', icon: CalendarDays },
42
+ { href: '/focus', label: '포커스', icon: Timer },
43
+ { href: '/review', label: '계획 & 회고', icon: BookOpen },
44
+ ]
45
+
46
+ export function GlobalSearch() {
47
+ const [open, setOpen] = useState(false)
48
+ const router = useRouter()
49
+
50
+ // Keyboard shortcut
51
+ useEffect(() => {
52
+ const handleKeyDown = (e: KeyboardEvent) => {
53
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
54
+ e.preventDefault()
55
+ setOpen(prev => !prev)
56
+ }
57
+ }
58
+ document.addEventListener('keydown', handleKeyDown)
59
+ return () => document.removeEventListener('keydown', handleKeyDown)
60
+ }, [])
61
+
62
+ // Data queries - only fetch when dialog is open
63
+ const { data: notes = [] } = useNotes()
64
+ const { data: tasks = [] } = useTasks()
65
+ const { data: goals = [] } = useGoals()
66
+ const { data: projects = [] } = useProjects()
67
+ const { data: books = [] } = useBooks()
68
+
69
+ const handleSelect = (href: string) => {
70
+ setOpen(false)
71
+ router.push(href)
72
+ }
73
+
74
+ return (
75
+ <CommandDialog
76
+ open={open}
77
+ onOpenChange={setOpen}
78
+ title="검색"
79
+ description="페이지, 노트, 할일, 목표를 검색하세요"
80
+ showCloseButton={false}
81
+ >
82
+ <CommandInput placeholder="검색..." />
83
+ <CommandList>
84
+ <CommandEmpty>검색 결과가 없습니다</CommandEmpty>
85
+
86
+ <CommandGroup heading="페이지">
87
+ {PAGES.map(page => {
88
+ const Icon = page.icon
89
+ return (
90
+ <CommandItem
91
+ key={page.href}
92
+ value={`page-${page.label}`}
93
+ onSelect={() => handleSelect(page.href)}
94
+ >
95
+ <Icon className="w-4 h-4 mr-2" />
96
+ {page.label}
97
+ </CommandItem>
98
+ )
99
+ })}
100
+ </CommandGroup>
101
+
102
+ {notes.length > 0 && (
103
+ <CommandGroup heading="노트">
104
+ {notes.map(note => (
105
+ <CommandItem
106
+ key={note.id}
107
+ value={`note-${note.title}`}
108
+ onSelect={() => handleSelect(`/notes/${note.id}`)}
109
+ >
110
+ <StickyNote className="w-4 h-4 mr-2" />
111
+ {note.title}
112
+ </CommandItem>
113
+ ))}
114
+ </CommandGroup>
115
+ )}
116
+
117
+ {tasks.length > 0 && (
118
+ <CommandGroup heading="할일">
119
+ {tasks.map(task => (
120
+ <CommandItem
121
+ key={task.id}
122
+ value={`task-${task.title}`}
123
+ onSelect={() => handleSelect(`/tasks/${task.id}`)}
124
+ >
125
+ <CheckSquare className="w-4 h-4 mr-2" />
126
+ {task.title}
127
+ </CommandItem>
128
+ ))}
129
+ </CommandGroup>
130
+ )}
131
+
132
+ {goals.length > 0 && (
133
+ <CommandGroup heading="목표">
134
+ {goals.map(goal => (
135
+ <CommandItem
136
+ key={goal.id}
137
+ value={`goal-${goal.title}`}
138
+ onSelect={() => handleSelect(`/goals/${goal.id}`)}
139
+ >
140
+ <Target className="w-4 h-4 mr-2" />
141
+ {goal.title}
142
+ </CommandItem>
143
+ ))}
144
+ </CommandGroup>
145
+ )}
146
+
147
+ {projects.length > 0 && (
148
+ <CommandGroup heading="프로젝트">
149
+ {projects.map(project => (
150
+ <CommandItem
151
+ key={project.id}
152
+ value={`project-${project.title}`}
153
+ onSelect={() => handleSelect(`/projects/${project.id}`)}
154
+ >
155
+ <FolderKanban className="w-4 h-4 mr-2" />
156
+ {project.title}
157
+ </CommandItem>
158
+ ))}
159
+ </CommandGroup>
160
+ )}
161
+
162
+ {books.length > 0 && (
163
+ <CommandGroup heading="독서">
164
+ {books.map(book => (
165
+ <CommandItem
166
+ key={book.id}
167
+ value={`book-${book.title} ${book.author}`}
168
+ onSelect={() => handleSelect(`/books/${book.id}`)}
169
+ >
170
+ <BookMarked className="w-4 h-4 mr-2" />
171
+ {book.title}
172
+ </CommandItem>
173
+ ))}
174
+ </CommandGroup>
175
+ )}
176
+ </CommandList>
177
+ </CommandDialog>
178
+ )
179
+ }
@@ -0,0 +1,77 @@
1
+ 'use client'
2
+
3
+ import { useState, useRef, useEffect } from 'react'
4
+ import { cn } from '@/lib/utils'
5
+
6
+ interface InlineEditProps {
7
+ value: string
8
+ onSave: (newValue: string) => void
9
+ className?: string
10
+ inputClassName?: string
11
+ placeholder?: string
12
+ }
13
+
14
+ export function InlineEdit({
15
+ value,
16
+ onSave,
17
+ className,
18
+ inputClassName,
19
+ placeholder = '입력...',
20
+ }: InlineEditProps) {
21
+ const [editing, setEditing] = useState(false)
22
+ const [editValue, setEditValue] = useState(value)
23
+ const inputRef = useRef<HTMLInputElement>(null)
24
+
25
+ useEffect(() => {
26
+ if (editing && inputRef.current) {
27
+ inputRef.current.focus()
28
+ inputRef.current.select()
29
+ }
30
+ }, [editing])
31
+
32
+ const handleSave = () => {
33
+ const trimmed = editValue.trim()
34
+ if (trimmed && trimmed !== value) {
35
+ onSave(trimmed)
36
+ }
37
+ setEditing(false)
38
+ }
39
+
40
+ const handleCancel = () => {
41
+ setEditValue(value)
42
+ setEditing(false)
43
+ }
44
+
45
+ if (editing) {
46
+ return (
47
+ <input
48
+ ref={inputRef}
49
+ value={editValue}
50
+ onChange={(e) => setEditValue(e.target.value)}
51
+ onBlur={handleSave}
52
+ onKeyDown={(e) => {
53
+ if (e.key === 'Enter') handleSave()
54
+ if (e.key === 'Escape') handleCancel()
55
+ }}
56
+ className={cn(
57
+ 'bg-transparent border-b border-primary/50 outline-none px-0 py-0.5',
58
+ inputClassName
59
+ )}
60
+ placeholder={placeholder}
61
+ />
62
+ )
63
+ }
64
+
65
+ return (
66
+ <span
67
+ className={cn('cursor-pointer hover:text-primary/80 transition-colors', className)}
68
+ onDoubleClick={() => {
69
+ setEditValue(value)
70
+ setEditing(true)
71
+ }}
72
+ title="더블클릭하여 편집"
73
+ >
74
+ {value}
75
+ </span>
76
+ )
77
+ }
@@ -0,0 +1,42 @@
1
+ 'use client'
2
+
3
+ import { Star } from 'lucide-react'
4
+ import { cn } from '@/lib/utils'
5
+ import { useFavoritesStore, type FavoriteType } from '@/stores/favoritesStore'
6
+
7
+ interface PinButtonProps {
8
+ type: FavoriteType
9
+ id: string
10
+ title: string
11
+ className?: string
12
+ size?: number
13
+ }
14
+
15
+ export function PinButton({ type, id, title, className, size = 16 }: PinButtonProps) {
16
+ const { toggle, isFavorite } = useFavoritesStore()
17
+ const pinned = isFavorite(type, id)
18
+
19
+ return (
20
+ <button
21
+ type="button"
22
+ onClick={(e) => {
23
+ e.stopPropagation()
24
+ e.preventDefault()
25
+ toggle({ type, id, title })
26
+ }}
27
+ className={cn(
28
+ 'transition-colors',
29
+ pinned
30
+ ? 'text-gatsaeng-amber'
31
+ : 'text-muted-foreground/40 hover:text-gatsaeng-amber/70',
32
+ className
33
+ )}
34
+ title={pinned ? '즐겨찾기 해제' : '즐겨찾기'}
35
+ >
36
+ <Star
37
+ style={{ width: size, height: size }}
38
+ className={cn(pinned && 'fill-current')}
39
+ />
40
+ </button>
41
+ )
42
+ }
@@ -0,0 +1,34 @@
1
+ import { cn } from '@/lib/utils'
2
+
3
+ interface DDayBadgeProps {
4
+ dueDate: string
5
+ className?: string
6
+ }
7
+
8
+ export function DDayBadge({ dueDate, className }: DDayBadgeProps) {
9
+ const today = new Date()
10
+ today.setHours(0, 0, 0, 0)
11
+ const due = new Date(dueDate)
12
+ due.setHours(0, 0, 0, 0)
13
+ const diff = Math.ceil((due.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
14
+
15
+ const label = diff === 0 ? 'D-Day' : diff > 0 ? `D-${diff}` : `D+${Math.abs(diff)}`
16
+
17
+ const color = diff < 0
18
+ ? 'bg-gatsaeng-red/20 text-gatsaeng-red border-gatsaeng-red/30'
19
+ : diff <= 3
20
+ ? 'bg-gatsaeng-red/10 text-gatsaeng-red border-gatsaeng-red/20'
21
+ : diff <= 7
22
+ ? 'bg-gatsaeng-amber/10 text-gatsaeng-amber border-gatsaeng-amber/20'
23
+ : 'bg-muted text-muted-foreground border-border'
24
+
25
+ return (
26
+ <span className={cn(
27
+ 'inline-flex items-center rounded-md border px-1.5 py-0.5 text-[10px] font-bold tabular-nums',
28
+ color,
29
+ className
30
+ )}>
31
+ {label}
32
+ </span>
33
+ )
34
+ }