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,475 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
5
+ import { Card, CardContent } from '@/components/ui/card'
6
+ import { Button } from '@/components/ui/button'
7
+ import { Label } from '@/components/ui/label'
8
+ import { Badge } from '@/components/ui/badge'
9
+ import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
10
+ import { BookOpen, ChevronRight, Star, Calendar, Sun, CalendarDays, ArrowLeft, Pencil, Trash2 } from 'lucide-react'
11
+ import { cn } from '@/lib/utils'
12
+ import type { Review } from '@/types'
13
+
14
+ const WEEKLY_PROMPTS = [
15
+ { key: 'accomplished', label: '이번 주 가장 잘한 것', placeholder: '구체적인 성취를 적어주세요', icon: '🏆' },
16
+ { key: 'struggled', label: '어디서 막혔는가', placeholder: '어떤 상황에서 어려움을 느꼈나요?', icon: '🧱' },
17
+ { key: 'learnings', label: '배운 점', placeholder: '이번 주 가장 큰 깨달음은?', icon: '💡' },
18
+ { key: 'next_week_focus', label: '다음 주 포커스', placeholder: '다음 주에 집중할 한 가지는?', icon: '🎯' },
19
+ { key: 'energy_pattern', label: '에너지 패턴', placeholder: '언제 에너지가 높았고, 언제 낮았나요?', icon: '⚡' },
20
+ { key: 'habit_insight', label: '루틴 인사이트', placeholder: '습관 유지에 도움이 된 것 / 방해한 것은?', icon: '🔄' },
21
+ ]
22
+
23
+ const DAILY_PROMPTS = [
24
+ { key: 'accomplished', label: '오늘 가장 잘한 것', placeholder: '오늘의 작은 성취를 적어주세요', icon: '🏆' },
25
+ { key: 'struggled', label: '어려웠던 점', placeholder: '오늘 어떤 부분이 어려웠나요?', icon: '🧱' },
26
+ { key: 'learnings', label: '오늘의 깨달음', placeholder: '오늘 배운 것은?', icon: '💡' },
27
+ { key: 'next_week_focus', label: '내일 포커스', placeholder: '내일 가장 중요한 한 가지는?', icon: '🎯' },
28
+ ]
29
+
30
+ const MONTHLY_PROMPTS = [
31
+ { key: 'accomplished', label: '이번 달 주요 성취', placeholder: '이번 달 가장 의미 있는 성취는?', icon: '🏆' },
32
+ { key: 'struggled', label: '반복된 어려움', placeholder: '이번 달 반복적으로 막힌 부분은?', icon: '🧱' },
33
+ { key: 'learnings', label: '핵심 교훈', placeholder: '이번 달의 핵심 교훈은?', icon: '💡' },
34
+ { key: 'next_week_focus', label: '다음 달 방향', placeholder: '다음 달 전략과 집중 방향은?', icon: '🎯' },
35
+ { key: 'energy_pattern', label: '에너지/생산성 패턴', placeholder: '이번 달 에너지와 생산성 패턴 분석', icon: '⚡' },
36
+ { key: 'habit_insight', label: '습관/루틴 점검', placeholder: '유지할 습관과 바꿀 습관은?', icon: '🔄' },
37
+ ]
38
+
39
+ type ReviewTab = 'daily' | 'weekly' | 'monthly'
40
+ const PROMPTS_MAP: Record<ReviewTab, typeof WEEKLY_PROMPTS> = {
41
+ daily: DAILY_PROMPTS,
42
+ weekly: WEEKLY_PROMPTS,
43
+ monthly: MONTHLY_PROMPTS,
44
+ }
45
+ const TAB_LABELS: Record<ReviewTab, string> = { daily: '일간 리뷰', weekly: '주간 리뷰', monthly: '월간 리뷰' }
46
+
47
+ function getWeekStart(date: Date = new Date()) {
48
+ const d = new Date(date)
49
+ const day = d.getDay()
50
+ const diff = d.getDate() - day + (day === 0 ? -6 : 1)
51
+ d.setDate(diff)
52
+ return d.toISOString().slice(0, 10)
53
+ }
54
+
55
+ export default function ReviewPage() {
56
+ const queryClient = useQueryClient()
57
+ const [mode, setMode] = useState<'list' | 'write' | 'view'>('list')
58
+ const [reviewTab, setReviewTab] = useState<ReviewTab>('weekly')
59
+ const [formData, setFormData] = useState<Record<string, string>>({})
60
+ const [mood, setMood] = useState(3)
61
+ const [score, setScore] = useState(7)
62
+ const [editingReview, setEditingReview] = useState<Review | null>(null)
63
+ const [viewingReview, setViewingReview] = useState<Review | null>(null)
64
+
65
+ const { data: reviews } = useQuery({
66
+ queryKey: ['reviews'],
67
+ queryFn: async (): Promise<Review[]> => {
68
+ const res = await fetch('/api/reviews')
69
+ return res.json()
70
+ },
71
+ })
72
+
73
+ const createReview = useMutation({
74
+ mutationFn: async (data: Record<string, unknown>) => {
75
+ const res = await fetch('/api/reviews', {
76
+ method: 'POST',
77
+ headers: { 'Content-Type': 'application/json' },
78
+ body: JSON.stringify(data),
79
+ })
80
+ return res.json()
81
+ },
82
+ onSuccess: () => {
83
+ queryClient.invalidateQueries({ queryKey: ['reviews'] })
84
+ setMode('list')
85
+ setFormData({})
86
+ setEditingReview(null)
87
+ },
88
+ })
89
+
90
+ const updateReview = useMutation({
91
+ mutationFn: async ({ id, ...data }: Record<string, unknown> & { id: string }) => {
92
+ const res = await fetch(`/api/reviews/${id}`, {
93
+ method: 'PUT',
94
+ headers: { 'Content-Type': 'application/json' },
95
+ body: JSON.stringify(data),
96
+ })
97
+ return res.json()
98
+ },
99
+ onSuccess: () => {
100
+ queryClient.invalidateQueries({ queryKey: ['reviews'] })
101
+ setMode('list')
102
+ setFormData({})
103
+ setEditingReview(null)
104
+ },
105
+ })
106
+
107
+ const deleteReview = useMutation({
108
+ mutationFn: async (id: string) => {
109
+ await fetch(`/api/reviews/${id}`, { method: 'DELETE' })
110
+ },
111
+ onSuccess: () => {
112
+ queryClient.invalidateQueries({ queryKey: ['reviews'] })
113
+ setMode('list')
114
+ setViewingReview(null)
115
+ },
116
+ })
117
+
118
+ const handleSubmit = () => {
119
+ const today = new Date().toISOString().slice(0, 10)
120
+ const base: Record<string, unknown> = { mood, score, ...formData }
121
+
122
+ if (editingReview?.id) {
123
+ // Update existing
124
+ const tab = (editingReview.type as ReviewTab) || 'weekly'
125
+ if (tab === 'daily') {
126
+ base.type = 'daily'
127
+ base.date = editingReview.date || today
128
+ } else if (tab === 'monthly') {
129
+ base.type = 'monthly'
130
+ base.week_start = editingReview.week_start || today.slice(0, 7)
131
+ } else {
132
+ base.type = 'weekly'
133
+ base.week_start = editingReview.week_start || getWeekStart()
134
+ }
135
+ updateReview.mutate({ id: editingReview.id, ...base })
136
+ } else {
137
+ // Create new
138
+ if (reviewTab === 'daily') {
139
+ base.type = 'daily'
140
+ base.date = today
141
+ } else if (reviewTab === 'monthly') {
142
+ base.type = 'monthly'
143
+ base.week_start = today.slice(0, 7)
144
+ } else {
145
+ base.type = 'weekly'
146
+ base.week_start = getWeekStart()
147
+ }
148
+ createReview.mutate(base)
149
+ }
150
+ }
151
+
152
+ const handleEdit = (review: Review) => {
153
+ setEditingReview(review)
154
+ const tab = (review.type as ReviewTab) || 'weekly'
155
+ setReviewTab(tab)
156
+ setFormData({
157
+ accomplished: review.accomplished ?? '',
158
+ struggled: review.struggled ?? '',
159
+ learnings: review.learnings ?? '',
160
+ next_week_focus: review.next_week_focus ?? '',
161
+ energy_pattern: review.energy_pattern ?? '',
162
+ habit_insight: review.habit_insight ?? '',
163
+ })
164
+ setMood(review.mood ?? 3)
165
+ setScore(review.score ?? 7)
166
+ setMode('write')
167
+ }
168
+
169
+ const handleDelete = (id: string) => {
170
+ if (!confirm('이 회고를 삭제하시겠습니까?')) return
171
+ deleteReview.mutate(id)
172
+ }
173
+
174
+ const handleView = (review: Review) => {
175
+ setViewingReview(review)
176
+ setMode('view')
177
+ }
178
+
179
+ const weekStart = getWeekStart()
180
+ const today = new Date().toISOString().slice(0, 10)
181
+ const allReviews = reviews ?? []
182
+ const filteredReviews = allReviews.filter(r => {
183
+ if (reviewTab === 'daily') return r.type === 'daily' || r.date
184
+ if (reviewTab === 'monthly') return r.type === 'monthly'
185
+ return !r.type || r.type === 'weekly'
186
+ })
187
+ const thisWeekReview = reviewTab === 'weekly'
188
+ ? allReviews.find(r => r.week_start === weekStart && (!r.type || r.type === 'weekly'))
189
+ : reviewTab === 'daily'
190
+ ? allReviews.find(r => r.date === today && r.type === 'daily')
191
+ : allReviews.find(r => r.type === 'monthly' && r.week_start === today.slice(0, 7))
192
+ const sortedReviews = [...filteredReviews].sort((a, b) => (b.week_start ?? b.date ?? '').localeCompare(a.week_start ?? a.date ?? ''))
193
+
194
+ // View mode
195
+ if (mode === 'view' && viewingReview) {
196
+ const vTab = (viewingReview.type as ReviewTab) || 'weekly'
197
+ const prompts = PROMPTS_MAP[vTab]
198
+ return (
199
+ <div className="max-w-3xl mx-auto">
200
+ <div className="flex items-center justify-between mb-6">
201
+ <div>
202
+ <button onClick={() => { setMode('list'); setViewingReview(null) }} className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors mb-2">
203
+ <ArrowLeft className="w-3.5 h-3.5" /> 목록으로
204
+ </button>
205
+ <h1 className="text-2xl font-bold text-foreground">
206
+ {TAB_LABELS[vTab]}
207
+ </h1>
208
+ <p className="text-sm text-muted-foreground mt-1">
209
+ {viewingReview.date || viewingReview.week_start}
210
+ {viewingReview.mood && ` · 기분 ${viewingReview.mood}/5`}
211
+ {viewingReview.score && ` · 점수 ${viewingReview.score}/10`}
212
+ </p>
213
+ </div>
214
+ <div className="flex items-center gap-2">
215
+ <Button variant="outline" size="sm" onClick={() => handleEdit(viewingReview)}>
216
+ <Pencil className="w-3.5 h-3.5 mr-1" /> 수정
217
+ </Button>
218
+ <Button variant="outline" size="sm" className="text-gatsaeng-red hover:text-gatsaeng-red" onClick={() => viewingReview.id && handleDelete(viewingReview.id)}>
219
+ <Trash2 className="w-3.5 h-3.5 mr-1" /> 삭제
220
+ </Button>
221
+ </div>
222
+ </div>
223
+
224
+ <div className="space-y-4">
225
+ {prompts.map(prompt => {
226
+ const value = (viewingReview as unknown as Record<string, unknown>)[prompt.key] as string | undefined
227
+ if (!value) return null
228
+ return (
229
+ <Card key={prompt.key}>
230
+ <CardContent className="py-4">
231
+ <Label className="text-sm flex items-center gap-2 mb-2">
232
+ <span>{prompt.icon}</span>
233
+ {prompt.label}
234
+ </Label>
235
+ <p className="text-sm text-foreground whitespace-pre-wrap">{value}</p>
236
+ </CardContent>
237
+ </Card>
238
+ )
239
+ })}
240
+
241
+ {(viewingReview.mood || viewingReview.score) && (
242
+ <Card>
243
+ <CardContent className="py-4">
244
+ <div className="flex items-center gap-6">
245
+ {viewingReview.mood && (
246
+ <div>
247
+ <Label className="text-xs text-muted-foreground block mb-1">기분</Label>
248
+ <div className="flex items-center gap-0.5">
249
+ {Array.from({ length: viewingReview.mood }).map((_, i) => (
250
+ <Star key={i} className="w-4 h-4 fill-gatsaeng-amber text-gatsaeng-amber" />
251
+ ))}
252
+ </div>
253
+ </div>
254
+ )}
255
+ {viewingReview.score && (
256
+ <div>
257
+ <Label className="text-xs text-muted-foreground block mb-1">점수</Label>
258
+ <span className="text-lg font-bold text-primary">{viewingReview.score}/10</span>
259
+ </div>
260
+ )}
261
+ </div>
262
+ </CardContent>
263
+ </Card>
264
+ )}
265
+ </div>
266
+ </div>
267
+ )
268
+ }
269
+
270
+ // Write/Edit mode
271
+ if (mode === 'write') {
272
+ return (
273
+ <div className="max-w-3xl mx-auto">
274
+ <div className="flex items-center justify-between mb-6">
275
+ <div>
276
+ <h1 className="text-2xl font-bold text-foreground">
277
+ {editingReview ? `${TAB_LABELS[(editingReview.type as ReviewTab) || 'weekly']} 수정` : `${TAB_LABELS[reviewTab]} 작성`}
278
+ </h1>
279
+ <p className="text-sm text-muted-foreground mt-1">
280
+ {editingReview
281
+ ? (editingReview.date || editingReview.week_start)
282
+ : reviewTab === 'daily' ? today : reviewTab === 'monthly' ? today.slice(0, 7) : `${weekStart} 주`}
283
+ </p>
284
+ </div>
285
+ <Button variant="outline" onClick={() => { setMode('list'); setEditingReview(null); setFormData({}) }}>목록으로</Button>
286
+ </div>
287
+
288
+ <div className="space-y-4">
289
+ {PROMPTS_MAP[editingReview ? ((editingReview.type as ReviewTab) || 'weekly') : reviewTab].map(prompt => (
290
+ <Card key={prompt.key}>
291
+ <CardContent className="py-4">
292
+ <Label className="text-sm flex items-center gap-2 mb-2">
293
+ <span>{prompt.icon}</span>
294
+ {prompt.label}
295
+ </Label>
296
+ <textarea
297
+ className="w-full bg-transparent border border-border rounded-md px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground resize-none focus:outline-none focus:ring-1 focus:ring-ring"
298
+ rows={3}
299
+ placeholder={prompt.placeholder}
300
+ value={formData[prompt.key] ?? ''}
301
+ onChange={e => setFormData(prev => ({ ...prev, [prompt.key]: e.target.value }))}
302
+ />
303
+ </CardContent>
304
+ </Card>
305
+ ))}
306
+
307
+ {/* Mood & Score */}
308
+ <Card>
309
+ <CardContent className="py-4">
310
+ <div className="grid grid-cols-2 gap-6">
311
+ <div>
312
+ <Label className="text-sm mb-3 block">기분 (1-5)</Label>
313
+ <div className="flex gap-1">
314
+ {[1, 2, 3, 4, 5].map(v => (
315
+ <button
316
+ key={v}
317
+ onClick={() => setMood(v)}
318
+ className={`w-9 h-9 rounded-md flex items-center justify-center text-sm font-medium transition-colors ${
319
+ mood >= v
320
+ ? 'bg-gatsaeng-amber text-black'
321
+ : 'bg-secondary text-muted-foreground hover:bg-secondary/80'
322
+ }`}
323
+ >
324
+ {v}
325
+ </button>
326
+ ))}
327
+ </div>
328
+ </div>
329
+ <div>
330
+ <Label className="text-sm mb-3 block">점수 (1-10)</Label>
331
+ <div className="flex gap-1 flex-wrap">
332
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(v => (
333
+ <button
334
+ key={v}
335
+ onClick={() => setScore(v)}
336
+ className={`w-8 h-8 rounded-md flex items-center justify-center text-xs font-medium transition-colors ${
337
+ score >= v
338
+ ? 'bg-primary text-primary-foreground'
339
+ : 'bg-secondary text-muted-foreground hover:bg-secondary/80'
340
+ }`}
341
+ >
342
+ {v}
343
+ </button>
344
+ ))}
345
+ </div>
346
+ </div>
347
+ </div>
348
+ </CardContent>
349
+ </Card>
350
+
351
+ <Button
352
+ onClick={handleSubmit}
353
+ disabled={createReview.isPending || updateReview.isPending}
354
+ className="w-full bg-gatsaeng-purple hover:bg-gatsaeng-purple/80 text-white"
355
+ >
356
+ <BookOpen className="w-4 h-4 mr-2" />
357
+ {editingReview ? '회고 수정' : '회고 저장'}
358
+ </Button>
359
+ </div>
360
+ </div>
361
+ )
362
+ }
363
+
364
+ return (
365
+ <div className="max-w-3xl mx-auto">
366
+ <div className="flex items-center justify-between mb-6">
367
+ <div>
368
+ <h1 className="text-2xl font-bold text-foreground">계획 & 회고</h1>
369
+ <p className="text-sm text-muted-foreground mt-1">Deliberate Practice 기반 구조화된 회고</p>
370
+ </div>
371
+ {!thisWeekReview && (
372
+ <Button
373
+ onClick={() => { setEditingReview(null); setFormData({}); setMode('write') }}
374
+ className="bg-gatsaeng-purple hover:bg-gatsaeng-purple/80 text-white"
375
+ >
376
+ <BookOpen className="w-4 h-4 mr-2" /> {TAB_LABELS[reviewTab]}
377
+ </Button>
378
+ )}
379
+ </div>
380
+
381
+ <Tabs value={reviewTab} onValueChange={(v) => setReviewTab(v as ReviewTab)} className="mb-6">
382
+ <TabsList>
383
+ <TabsTrigger value="daily" className="gap-1.5">
384
+ <Sun className="w-3.5 h-3.5" /> 일간
385
+ </TabsTrigger>
386
+ <TabsTrigger value="weekly" className="gap-1.5">
387
+ <Calendar className="w-3.5 h-3.5" /> 주간
388
+ </TabsTrigger>
389
+ <TabsTrigger value="monthly" className="gap-1.5">
390
+ <CalendarDays className="w-3.5 h-3.5" /> 월간
391
+ </TabsTrigger>
392
+ </TabsList>
393
+ </Tabs>
394
+
395
+ {sortedReviews.length === 0 ? (
396
+ <Card>
397
+ <CardContent className="py-12 text-center">
398
+ <BookOpen className="w-8 h-8 mx-auto mb-3 text-muted-foreground" />
399
+ <p className="text-muted-foreground mb-4">아직 회고가 없습니다</p>
400
+ <Button onClick={() => { setEditingReview(null); setFormData({}); setMode('write') }} className="bg-gatsaeng-purple hover:bg-gatsaeng-purple/80 text-white">
401
+ 첫 회고 작성하기
402
+ </Button>
403
+ </CardContent>
404
+ </Card>
405
+ ) : (
406
+ <div className="space-y-3">
407
+ {sortedReviews.map(review => (
408
+ <Card
409
+ key={review.id}
410
+ className={cn(
411
+ 'hover:border-primary/30 transition-colors cursor-pointer group',
412
+ review.week_start === weekStart && 'border-gatsaeng-purple/30'
413
+ )}
414
+ onClick={() => handleView(review)}
415
+ >
416
+ <CardContent className="py-4">
417
+ <div className="flex items-center justify-between">
418
+ <div className="flex items-center gap-3">
419
+ <div className="w-10 h-10 rounded-lg bg-gatsaeng-purple/10 flex items-center justify-center">
420
+ <Calendar className="w-5 h-5 text-gatsaeng-purple" />
421
+ </div>
422
+ <div>
423
+ <div className="text-sm font-medium">{review.date || review.week_start}</div>
424
+ <div className="flex items-center gap-2 mt-0.5">
425
+ {review.mood && (
426
+ <div className="flex items-center gap-0.5">
427
+ {Array.from({ length: review.mood }).map((_, i) => (
428
+ <Star key={i} className="w-3 h-3 fill-gatsaeng-amber text-gatsaeng-amber" />
429
+ ))}
430
+ </div>
431
+ )}
432
+ {review.score && (
433
+ <Badge variant="outline" className="text-[10px]">
434
+ {review.score}/10
435
+ </Badge>
436
+ )}
437
+ </div>
438
+ </div>
439
+ </div>
440
+ <div className="flex items-center gap-2">
441
+ {review.week_start === weekStart && (
442
+ <Badge className="text-[10px] bg-gatsaeng-purple/10 text-gatsaeng-purple border-gatsaeng-purple/30">
443
+ 이번 주
444
+ </Badge>
445
+ )}
446
+ <button
447
+ onClick={(e) => { e.stopPropagation(); handleEdit(review) }}
448
+ className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-primary/10 rounded"
449
+ >
450
+ <Pencil className="w-3.5 h-3.5 text-muted-foreground" />
451
+ </button>
452
+ <button
453
+ onClick={(e) => { e.stopPropagation(); review.id && handleDelete(review.id) }}
454
+ className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-destructive/10 rounded"
455
+ >
456
+ <Trash2 className="w-3.5 h-3.5 text-gatsaeng-red" />
457
+ </button>
458
+ <ChevronRight className="w-4 h-4 text-muted-foreground" />
459
+ </div>
460
+ </div>
461
+
462
+ {/* Preview of content */}
463
+ {review.accomplished && (
464
+ <p className="text-xs text-muted-foreground mt-2 line-clamp-1">
465
+ 🏆 {review.accomplished}
466
+ </p>
467
+ )}
468
+ </CardContent>
469
+ </Card>
470
+ ))}
471
+ </div>
472
+ )}
473
+ </div>
474
+ )
475
+ }