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,147 @@
1
+ 'use client'
2
+
3
+ import { use, useState, useCallback } from 'react'
4
+ import { useRouter } from 'next/navigation'
5
+ import { Card, CardContent } from '@/components/ui/card'
6
+ import { Button } from '@/components/ui/button'
7
+ import { Badge } from '@/components/ui/badge'
8
+ import { Input } from '@/components/ui/input'
9
+ import { ArrowLeft, Trash2 } from 'lucide-react'
10
+ import { useNote, useUpdateNote, useDeleteNote } from '@/hooks/useNotes'
11
+ import { TiptapEditor } from '@/components/editor/TiptapEditor'
12
+ import { PinButton } from '@/components/shared/PinButton'
13
+ import type { NoteType, NotePriority } from '@/types'
14
+
15
+ const TYPE_LABELS: Record<NoteType, string> = {
16
+ note: '노트',
17
+ file: '파일',
18
+ reference: '레퍼런스',
19
+ link: '링크',
20
+ }
21
+
22
+ const PRIORITY_COLORS: Record<NotePriority, string> = {
23
+ 1: 'border-gatsaeng-red/30 text-gatsaeng-red',
24
+ 2: 'border-gatsaeng-amber/30 text-gatsaeng-amber',
25
+ 3: 'border-gatsaeng-teal/30 text-gatsaeng-teal',
26
+ }
27
+
28
+ export default function NoteDetailPage({ params }: { params: Promise<{ id: string }> }) {
29
+ const { id } = use(params)
30
+ const router = useRouter()
31
+ const { data: note, isLoading } = useNote(id)
32
+ const updateNote = useUpdateNote()
33
+ const deleteNote = useDeleteNote()
34
+ const [editingTitle, setEditingTitle] = useState(false)
35
+ const [titleValue, setTitleValue] = useState('')
36
+
37
+ const handleTitleSave = useCallback(() => {
38
+ if (!note || !titleValue.trim()) return
39
+ if (titleValue !== note.title) {
40
+ updateNote.mutate({ id, title: titleValue })
41
+ }
42
+ setEditingTitle(false)
43
+ }, [id, note, titleValue, updateNote])
44
+
45
+ const handleContentSave = useCallback((markdown: string) => {
46
+ updateNote.mutate({ id, content: markdown })
47
+ }, [id, updateNote])
48
+
49
+ const handleDelete = () => {
50
+ if (!confirm('이 노트를 삭제하시겠습니까?')) return
51
+ deleteNote.mutate(id, {
52
+ onSuccess: () => router.push('/notes'),
53
+ })
54
+ }
55
+
56
+ if (isLoading) {
57
+ return (
58
+ <div className="flex items-center justify-center h-64">
59
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
60
+ </div>
61
+ )
62
+ }
63
+
64
+ if (!note) {
65
+ return (
66
+ <div className="text-center py-20">
67
+ <p className="text-muted-foreground">노트를 찾을 수 없습니다</p>
68
+ <Button variant="outline" className="mt-4" onClick={() => router.push('/notes')}>
69
+ <ArrowLeft className="w-4 h-4 mr-2" /> 돌아가기
70
+ </Button>
71
+ </div>
72
+ )
73
+ }
74
+
75
+ return (
76
+ <div className="max-w-3xl mx-auto">
77
+ {/* Header */}
78
+ <div className="flex items-center gap-3 mb-6">
79
+ <Button variant="ghost" size="icon" onClick={() => router.push('/notes')}>
80
+ <ArrowLeft className="w-5 h-5" />
81
+ </Button>
82
+ <div className="flex-1 min-w-0">
83
+ {editingTitle ? (
84
+ <Input
85
+ value={titleValue}
86
+ onChange={(e) => setTitleValue(e.target.value)}
87
+ onBlur={handleTitleSave}
88
+ onKeyDown={(e) => {
89
+ if (e.key === 'Enter') handleTitleSave()
90
+ if (e.key === 'Escape') setEditingTitle(false)
91
+ }}
92
+ autoFocus
93
+ className="text-2xl font-bold h-auto py-1"
94
+ />
95
+ ) : (
96
+ <h1
97
+ className="text-2xl font-bold text-foreground cursor-pointer hover:text-primary/80 transition-colors truncate"
98
+ onDoubleClick={() => {
99
+ setTitleValue(note.title)
100
+ setEditingTitle(true)
101
+ }}
102
+ title="더블클릭하여 편집"
103
+ >
104
+ {note.title}
105
+ </h1>
106
+ )}
107
+ </div>
108
+ <PinButton type="note" id={id} title={note.title} size={20} />
109
+ <Button variant="ghost" size="icon" onClick={handleDelete} className="text-muted-foreground hover:text-gatsaeng-red">
110
+ <Trash2 className="w-4 h-4" />
111
+ </Button>
112
+ </div>
113
+
114
+ {/* Meta */}
115
+ <div className="flex items-center gap-2 mb-6 flex-wrap">
116
+ <Badge variant="outline">{TYPE_LABELS[note.type] ?? note.type}</Badge>
117
+ <Badge variant="outline" className={PRIORITY_COLORS[note.priority]}>
118
+ P{note.priority}
119
+ </Badge>
120
+ {note.url && (
121
+ <a
122
+ href={note.url}
123
+ target="_blank"
124
+ rel="noopener noreferrer"
125
+ className="text-xs text-primary hover:underline truncate max-w-xs"
126
+ >
127
+ {note.url}
128
+ </a>
129
+ )}
130
+ <span className="text-xs text-muted-foreground ml-auto">
131
+ {note.created_at?.slice(0, 10)}
132
+ </span>
133
+ </div>
134
+
135
+ {/* Editor */}
136
+ <Card>
137
+ <CardContent className="p-0">
138
+ <TiptapEditor
139
+ content={note._content ?? ''}
140
+ onSave={handleContentSave}
141
+ placeholder="노트 내용을 입력하세요..."
142
+ />
143
+ </CardContent>
144
+ </Card>
145
+ </div>
146
+ )
147
+ }
@@ -0,0 +1,254 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useRouter } from 'next/navigation'
5
+ import { useNotes, useCreateNote, useDeleteNote } from '@/hooks/useNotes'
6
+ import { useAreas } from '@/hooks/useAreas'
7
+ import { Button } from '@/components/ui/button'
8
+ import { Card, CardContent } from '@/components/ui/card'
9
+ import { Input } from '@/components/ui/input'
10
+ import { Label } from '@/components/ui/label'
11
+ import { Badge } from '@/components/ui/badge'
12
+ import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
13
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
14
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
15
+ import { StickyNote, FileText, BookOpen, Link2, Plus, Trash2, ExternalLink } from 'lucide-react'
16
+ import { cn } from '@/lib/utils'
17
+ import type { NoteType } from '@/types'
18
+
19
+ const TYPE_CONFIG: Record<NoteType, { label: string; icon: typeof StickyNote; color: string }> = {
20
+ note: { label: '노트', icon: StickyNote, color: 'bg-primary/10 text-primary border-primary/30' },
21
+ file: { label: '파일', icon: FileText, color: 'bg-gatsaeng-amber/10 text-gatsaeng-amber border-gatsaeng-amber/30' },
22
+ reference: { label: '레퍼런스', icon: BookOpen, color: 'bg-gatsaeng-teal/10 text-gatsaeng-teal border-gatsaeng-teal/30' },
23
+ link: { label: '링크', icon: Link2, color: 'bg-gatsaeng-purple/10 text-gatsaeng-purple border-gatsaeng-purple/30' },
24
+ }
25
+
26
+ const PRIORITY_COLORS: Record<number, string> = {
27
+ 1: 'bg-gatsaeng-red text-white',
28
+ 2: 'bg-gatsaeng-amber text-black',
29
+ 3: 'bg-muted text-muted-foreground',
30
+ }
31
+
32
+ export default function NotesPage() {
33
+ const [tab, setTab] = useState<string>('all')
34
+ const [open, setOpen] = useState(false)
35
+ const { data: notes = [], isLoading } = useNotes(tab === 'all' ? undefined : tab as NoteType)
36
+ const { data: areas = [] } = useAreas()
37
+ const createNote = useCreateNote()
38
+ const deleteNote = useDeleteNote()
39
+
40
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
41
+ e.preventDefault()
42
+ const form = new FormData(e.currentTarget)
43
+ await createNote.mutateAsync({
44
+ title: form.get('title') as string,
45
+ type: (form.get('type') as NoteType) || 'note',
46
+ priority: (Number(form.get('priority')) || 2) as 1 | 2 | 3,
47
+ area_id: (form.get('area_id') as string) || undefined,
48
+ url: (form.get('url') as string) || undefined,
49
+ content: (form.get('content') as string) || undefined,
50
+ })
51
+ setOpen(false)
52
+ }
53
+
54
+ const handleDelete = (id: string, title: string) => {
55
+ if (!confirm(`"${title}" 노트를 삭제하시겠습니까?`)) return
56
+ deleteNote.mutate(id)
57
+ }
58
+
59
+ const grouped = {
60
+ note: notes.filter(n => n.type === 'note'),
61
+ file: notes.filter(n => n.type === 'file'),
62
+ reference: notes.filter(n => n.type === 'reference'),
63
+ link: notes.filter(n => n.type === 'link'),
64
+ }
65
+
66
+ return (
67
+ <div className="max-w-4xl mx-auto">
68
+ <div className="flex items-center justify-between mb-6">
69
+ <div>
70
+ <h1 className="text-2xl font-bold text-foreground">노트</h1>
71
+ <p className="text-sm text-muted-foreground mt-1">노트, 파일, 레퍼런스, 링크 관리</p>
72
+ </div>
73
+ <Dialog open={open} onOpenChange={setOpen}>
74
+ <DialogTrigger asChild>
75
+ <Button className="bg-primary hover:bg-primary/80">
76
+ <Plus className="w-4 h-4 mr-2" /> 노트 추가
77
+ </Button>
78
+ </DialogTrigger>
79
+ <DialogContent className="max-w-lg">
80
+ <DialogHeader>
81
+ <DialogTitle>노트 추가</DialogTitle>
82
+ </DialogHeader>
83
+ <form onSubmit={handleSubmit} className="space-y-4">
84
+ <div>
85
+ <Label>제목</Label>
86
+ <Input name="title" required placeholder="노트 제목" />
87
+ </div>
88
+ <div className="grid grid-cols-2 gap-4">
89
+ <div>
90
+ <Label>유형</Label>
91
+ <Select name="type" defaultValue="note">
92
+ <SelectTrigger><SelectValue /></SelectTrigger>
93
+ <SelectContent>
94
+ {(Object.keys(TYPE_CONFIG) as NoteType[]).map(t => (
95
+ <SelectItem key={t} value={t}>{TYPE_CONFIG[t].label}</SelectItem>
96
+ ))}
97
+ </SelectContent>
98
+ </Select>
99
+ </div>
100
+ <div>
101
+ <Label>우선순위</Label>
102
+ <Select name="priority" defaultValue="2">
103
+ <SelectTrigger><SelectValue /></SelectTrigger>
104
+ <SelectContent>
105
+ <SelectItem value="1">P1 (높음)</SelectItem>
106
+ <SelectItem value="2">P2 (보통)</SelectItem>
107
+ <SelectItem value="3">P3 (낮음)</SelectItem>
108
+ </SelectContent>
109
+ </Select>
110
+ </div>
111
+ </div>
112
+ <div>
113
+ <Label>영역 (선택)</Label>
114
+ <Select name="area_id">
115
+ <SelectTrigger><SelectValue placeholder="없음" /></SelectTrigger>
116
+ <SelectContent>
117
+ <SelectItem value="">없음</SelectItem>
118
+ {areas.map((a: { id: string; title: string }) => (
119
+ <SelectItem key={a.id} value={a.id}>{a.title}</SelectItem>
120
+ ))}
121
+ </SelectContent>
122
+ </Select>
123
+ </div>
124
+ <div>
125
+ <Label>URL (링크/레퍼런스)</Label>
126
+ <Input name="url" type="url" placeholder="https://..." />
127
+ </div>
128
+ <div>
129
+ <Label>내용</Label>
130
+ <textarea
131
+ name="content"
132
+ placeholder="마크다운 내용..."
133
+ className="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
134
+ rows={4}
135
+ />
136
+ </div>
137
+ <Button type="submit" className="w-full">추가</Button>
138
+ </form>
139
+ </DialogContent>
140
+ </Dialog>
141
+ </div>
142
+
143
+ <Tabs value={tab} onValueChange={setTab} className="mb-6">
144
+ <TabsList>
145
+ <TabsTrigger value="all">전체 ({notes.length})</TabsTrigger>
146
+ <TabsTrigger value="note">노트</TabsTrigger>
147
+ <TabsTrigger value="file">파일</TabsTrigger>
148
+ <TabsTrigger value="reference">레퍼런스</TabsTrigger>
149
+ <TabsTrigger value="link">링크</TabsTrigger>
150
+ </TabsList>
151
+ </Tabs>
152
+
153
+ {isLoading ? (
154
+ <div className="space-y-3">
155
+ {[1, 2, 3].map(i => <div key={i} className="h-16 bg-card rounded-lg animate-pulse" />)}
156
+ </div>
157
+ ) : notes.length === 0 ? (
158
+ <Card>
159
+ <CardContent className="py-12 text-center text-muted-foreground">
160
+ 노트를 추가하세요
161
+ </CardContent>
162
+ </Card>
163
+ ) : tab === 'all' ? (
164
+ // Grouped view when showing all
165
+ <div className="space-y-6">
166
+ {(Object.keys(TYPE_CONFIG) as NoteType[]).map(type => {
167
+ const items = grouped[type]
168
+ if (items.length === 0) return null
169
+ const config = TYPE_CONFIG[type]
170
+ const Icon = config.icon
171
+ return (
172
+ <div key={type}>
173
+ <div className="flex items-center gap-2 mb-3">
174
+ <Icon className="w-4 h-4 text-muted-foreground" />
175
+ <span className="text-sm font-medium">{config.label}</span>
176
+ <Badge variant="outline" className="text-[10px]">{items.length}</Badge>
177
+ </div>
178
+ <div className="space-y-2">
179
+ {items.map(note => (
180
+ <NoteCard key={note.id} note={note} areas={areas} onDelete={handleDelete} />
181
+ ))}
182
+ </div>
183
+ </div>
184
+ )
185
+ })}
186
+ </div>
187
+ ) : (
188
+ // Flat list for filtered view
189
+ <div className="space-y-2">
190
+ {notes.map(note => (
191
+ <NoteCard key={note.id} note={note} areas={areas} onDelete={handleDelete} />
192
+ ))}
193
+ </div>
194
+ )}
195
+ </div>
196
+ )
197
+ }
198
+
199
+ function NoteCard({ note, areas, onDelete }: {
200
+ note: { id: string; title: string; type: NoteType; priority: number; area_id?: string; url?: string; created_at: string }
201
+ areas: { id: string; title: string }[]
202
+ onDelete: (id: string, title: string) => void
203
+ }) {
204
+ const router = useRouter()
205
+ const config = TYPE_CONFIG[note.type]
206
+ const Icon = config.icon
207
+ const area = areas.find(a => a.id === note.area_id)
208
+
209
+ return (
210
+ <Card
211
+ className="group hover:border-primary/30 transition-colors cursor-pointer"
212
+ onClick={() => router.push(`/notes/${note.id}`)}
213
+ >
214
+ <CardContent className="py-3 px-4">
215
+ <div className="flex items-center gap-3">
216
+ <div className={cn('w-8 h-8 rounded-md flex items-center justify-center border', config.color)}>
217
+ <Icon className="w-4 h-4" />
218
+ </div>
219
+ <div className="flex-1 min-w-0">
220
+ <div className="flex items-center gap-2">
221
+ <Badge className={cn('text-[10px] px-1.5 py-0', PRIORITY_COLORS[note.priority])}>
222
+ P{note.priority}
223
+ </Badge>
224
+ <span className="text-sm font-medium truncate">{note.title}</span>
225
+ </div>
226
+ <div className="flex items-center gap-2 mt-0.5">
227
+ {area && (
228
+ <span className="text-[10px] text-muted-foreground">{area.title}</span>
229
+ )}
230
+ {note.url && (
231
+ <a
232
+ href={note.url}
233
+ target="_blank"
234
+ rel="noopener noreferrer"
235
+ className="text-[10px] text-primary flex items-center gap-0.5 hover:underline"
236
+ onClick={e => e.stopPropagation()}
237
+ >
238
+ <ExternalLink className="w-2.5 h-2.5" />
239
+ <span className="truncate max-w-[200px]">{note.url}</span>
240
+ </a>
241
+ )}
242
+ </div>
243
+ </div>
244
+ <button
245
+ onClick={(e) => { e.stopPropagation(); onDelete(note.id, note.title) }}
246
+ className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-destructive/10 rounded"
247
+ >
248
+ <Trash2 className="w-3.5 h-3.5 text-gatsaeng-red" />
249
+ </button>
250
+ </div>
251
+ </CardContent>
252
+ </Card>
253
+ )
254
+ }
@@ -0,0 +1,26 @@
1
+ 'use client'
2
+
3
+ import { DashboardGrid } from '@/components/dashboard/DashboardGrid'
4
+ import { WidgetCustomizer } from '@/components/dashboard/WidgetCustomizer'
5
+
6
+ export default function DashboardPage() {
7
+ return (
8
+ <div className="flex flex-col h-full">
9
+ <div className="flex items-center justify-between mb-5">
10
+ <div>
11
+ <h1 className="text-xl font-bold text-foreground">
12
+ 대시보드
13
+ </h1>
14
+ <p className="text-xs text-muted-foreground mt-0.5">
15
+ 갓생 Mission Control
16
+ </p>
17
+ </div>
18
+ <WidgetCustomizer />
19
+ </div>
20
+
21
+ <div className="flex-1 overflow-auto">
22
+ <DashboardGrid />
23
+ </div>
24
+ </div>
25
+ )
26
+ }
@@ -0,0 +1,86 @@
1
+ 'use client'
2
+
3
+ import { use, useState } from 'react'
4
+ import { useQuery } from '@tanstack/react-query'
5
+ import { useTasks, useCreateTask, useUpdateTask } from '@/hooks/useProjects'
6
+ import { ViewSwitcher } from '@/components/projects/ViewSwitcher'
7
+ import { TaskForm } from '@/components/projects/TaskForm'
8
+ import { KanbanView } from '@/components/projects/KanbanView'
9
+ import { TableView } from '@/components/projects/TableView'
10
+ import { ListView } from '@/components/projects/ListView'
11
+ import { CalendarView } from '@/components/projects/CalendarView'
12
+ import { Badge } from '@/components/ui/badge'
13
+ import { ArrowLeft } from 'lucide-react'
14
+ import Link from 'next/link'
15
+ import type { Project, Task, ViewType, TaskStatus } from '@/types'
16
+
17
+ export default function ProjectDetailPage({ params }: { params: Promise<{ id: string }> }) {
18
+ const { id } = use(params)
19
+
20
+ const { data: project } = useQuery({
21
+ queryKey: ['project', id],
22
+ queryFn: async (): Promise<Project> => {
23
+ const res = await fetch(`/api/projects/${id}`)
24
+ return res.json()
25
+ },
26
+ })
27
+
28
+ const { data: tasks } = useTasks(id)
29
+ const createTask = useCreateTask()
30
+ const updateTask = useUpdateTask()
31
+
32
+ const [view, setView] = useState<ViewType>(project?.default_view ?? 'kanban')
33
+
34
+ const handleCreateTask = async (data: Partial<Task>) => {
35
+ await createTask.mutateAsync(data)
36
+ }
37
+
38
+ const handleUpdateTask = (data: { id: string; status: TaskStatus; position?: number }) => {
39
+ updateTask.mutate(data)
40
+ }
41
+
42
+ const allTasks = tasks ?? []
43
+
44
+ return (
45
+ <div className="max-w-6xl mx-auto">
46
+ <div className="mb-6">
47
+ <Link href="/projects" className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors mb-3">
48
+ <ArrowLeft className="w-3.5 h-3.5" />
49
+ 프로젝트 목록
50
+ </Link>
51
+ <div className="flex items-center justify-between flex-wrap gap-3">
52
+ <div>
53
+ <div className="flex items-center gap-3">
54
+ <h1 className="text-2xl font-bold text-foreground">{project?.title ?? '...'}</h1>
55
+ {project && <Badge variant="outline">{project.status}</Badge>}
56
+ </div>
57
+ {project?.description && (
58
+ <p className="text-sm text-muted-foreground mt-1">{project.description}</p>
59
+ )}
60
+ </div>
61
+ <div className="flex items-center gap-3">
62
+ <ViewSwitcher value={view} onChange={setView} />
63
+ <TaskForm
64
+ projectId={id}
65
+ defaultStatus={view === 'kanban' ? 'backlog' : 'todo'}
66
+ onSubmit={handleCreateTask}
67
+ />
68
+ </div>
69
+ </div>
70
+ </div>
71
+
72
+ {view === 'kanban' && (
73
+ <KanbanView tasks={allTasks} onUpdateTask={handleUpdateTask} />
74
+ )}
75
+ {view === 'table' && (
76
+ <TableView tasks={allTasks} onUpdateTask={handleUpdateTask} />
77
+ )}
78
+ {view === 'list' && (
79
+ <ListView tasks={allTasks} onUpdateTask={handleUpdateTask} />
80
+ )}
81
+ {view === 'calendar' && (
82
+ <CalendarView tasks={allTasks} />
83
+ )}
84
+ </div>
85
+ )
86
+ }