From e3832acfa8a3222bc649989c38a1f4062345a222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B3=E4=BD=A9=E5=BA=AD?= Date: Fri, 18 Jul 2025 13:07:28 +0800 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 27 + PRODUCTION-CHECKLIST.md | 139 +++++ README-DEPLOYMENT.md | 122 +++++ app/analytics/loading.tsx | 3 + app/analytics/page.tsx | 690 +++++++++++++++++++++++++ app/globals.css | 94 ++++ app/layout.tsx | 31 ++ app/page.tsx | 391 ++++++++++++++ app/submit/page.tsx | 541 ++++++++++++++++++++ app/test/page.tsx | 488 ++++++++++++++++++ app/thank-you/loading.tsx | 10 + app/thank-you/page.tsx | 434 ++++++++++++++++ app/wishes/loading.tsx | 3 + app/wishes/page.tsx | 413 +++++++++++++++ components.json | 21 + components/header-music-control.tsx | 138 +++++ components/radar-chart.tsx | 183 +++++++ components/theme-provider.tsx | 11 + components/ui/accordion.tsx | 58 +++ components/ui/alert-dialog.tsx | 141 +++++ components/ui/alert.tsx | 59 +++ components/ui/aspect-ratio.tsx | 7 + components/ui/avatar.tsx | 50 ++ components/ui/badge.tsx | 36 ++ components/ui/breadcrumb.tsx | 115 +++++ components/ui/button.tsx | 56 ++ components/ui/calendar.tsx | 66 +++ components/ui/card.tsx | 79 +++ components/ui/carousel.tsx | 262 ++++++++++ components/ui/chart.tsx | 365 +++++++++++++ components/ui/checkbox.tsx | 30 ++ components/ui/collapsible.tsx | 11 + components/ui/command.tsx | 153 ++++++ components/ui/context-menu.tsx | 200 ++++++++ components/ui/dialog.tsx | 122 +++++ components/ui/drawer.tsx | 118 +++++ components/ui/dropdown-menu.tsx | 200 ++++++++ components/ui/form.tsx | 178 +++++++ components/ui/hover-card.tsx | 29 ++ components/ui/input-otp.tsx | 71 +++ components/ui/input.tsx | 22 + components/ui/label.tsx | 26 + components/ui/menubar.tsx | 236 +++++++++ components/ui/navigation-menu.tsx | 128 +++++ components/ui/pagination.tsx | 117 +++++ components/ui/popover.tsx | 31 ++ components/ui/progress.tsx | 28 + components/ui/radio-group.tsx | 44 ++ components/ui/resizable.tsx | 45 ++ components/ui/scroll-area.tsx | 48 ++ components/ui/select.tsx | 160 ++++++ components/ui/separator.tsx | 31 ++ components/ui/sheet.tsx | 140 +++++ components/ui/sidebar.tsx | 763 ++++++++++++++++++++++++++++ components/ui/skeleton.tsx | 15 + components/ui/slider.tsx | 28 + components/ui/sonner.tsx | 31 ++ components/ui/switch.tsx | 29 ++ components/ui/table.tsx | 117 +++++ components/ui/tabs.tsx | 55 ++ components/ui/textarea.tsx | 22 + components/ui/toast.tsx | 129 +++++ components/ui/toaster.tsx | 35 ++ components/ui/toggle-group.tsx | 61 +++ components/ui/toggle.tsx | 45 ++ components/ui/tooltip.tsx | 30 ++ components/ui/use-mobile.tsx | 19 + components/ui/use-toast.ts | 194 +++++++ components/wish-card.tsx | 429 ++++++++++++++++ hooks/use-mobile.tsx | 19 + hooks/use-toast.ts | 194 +++++++ lib/background-music.ts | 250 +++++++++ lib/categorization.ts | 378 ++++++++++++++ lib/solution-recommendations.ts | 259 ++++++++++ lib/sound-effects.ts | 121 +++++ lib/utils.ts | 6 + next.config.mjs | 14 + package.json | 71 +++ pnpm-lock.yaml | 5 + postcss.config.mjs | 8 + public/placeholder-logo.png | Bin 0 -> 568 bytes public/placeholder-logo.svg | 1 + public/placeholder-user.jpg | Bin 0 -> 1635 bytes public/placeholder.jpg | Bin 0 -> 1064 bytes public/placeholder.svg | 1 + scripts/clear-all-data.js | 55 ++ scripts/reset-to-production.js | 42 ++ styles/globals.css | 94 ++++ tailwind.config.ts | 179 +++++++ tsconfig.json | 27 + 91 files changed, 10929 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 PRODUCTION-CHECKLIST.md create mode 100644 README-DEPLOYMENT.md create mode 100644 app/analytics/loading.tsx create mode 100644 app/analytics/page.tsx create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 app/submit/page.tsx create mode 100644 app/test/page.tsx create mode 100644 app/thank-you/loading.tsx create mode 100644 app/thank-you/page.tsx create mode 100644 app/wishes/loading.tsx create mode 100644 app/wishes/page.tsx create mode 100644 components.json create mode 100644 components/header-music-control.tsx create mode 100644 components/radar-chart.tsx create mode 100644 components/theme-provider.tsx create mode 100644 components/ui/accordion.tsx create mode 100644 components/ui/alert-dialog.tsx create mode 100644 components/ui/alert.tsx create mode 100644 components/ui/aspect-ratio.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/breadcrumb.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/carousel.tsx create mode 100644 components/ui/chart.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/collapsible.tsx create mode 100644 components/ui/command.tsx create mode 100644 components/ui/context-menu.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/drawer.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/hover-card.tsx create mode 100644 components/ui/input-otp.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/menubar.tsx create mode 100644 components/ui/navigation-menu.tsx create mode 100644 components/ui/pagination.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/progress.tsx create mode 100644 components/ui/radio-group.tsx create mode 100644 components/ui/resizable.tsx create mode 100644 components/ui/scroll-area.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/separator.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/sidebar.tsx create mode 100644 components/ui/skeleton.tsx create mode 100644 components/ui/slider.tsx create mode 100644 components/ui/sonner.tsx create mode 100644 components/ui/switch.tsx create mode 100644 components/ui/table.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/toast.tsx create mode 100644 components/ui/toaster.tsx create mode 100644 components/ui/toggle-group.tsx create mode 100644 components/ui/toggle.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 components/ui/use-mobile.tsx create mode 100644 components/ui/use-toast.ts create mode 100644 components/wish-card.tsx create mode 100644 hooks/use-mobile.tsx create mode 100644 hooks/use-toast.ts create mode 100644 lib/background-music.ts create mode 100644 lib/categorization.ts create mode 100644 lib/solution-recommendations.ts create mode 100644 lib/sound-effects.ts create mode 100644 lib/utils.ts create mode 100644 next.config.mjs create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 postcss.config.mjs create mode 100644 public/placeholder-logo.png create mode 100644 public/placeholder-logo.svg create mode 100644 public/placeholder-user.jpg create mode 100644 public/placeholder.jpg create mode 100644 public/placeholder.svg create mode 100644 scripts/clear-all-data.js create mode 100644 scripts/reset-to-production.js create mode 100644 styles/globals.css create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f650315 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/PRODUCTION-CHECKLIST.md b/PRODUCTION-CHECKLIST.md new file mode 100644 index 0000000..80bdbbd --- /dev/null +++ b/PRODUCTION-CHECKLIST.md @@ -0,0 +1,139 @@ +# 🚀 正式環境檢查清單 + +## 佈署前檢查 (Pre-deployment) + +### 📊 資料清理 +- [ ] 執行 `localStorage.clear()` 清空瀏覽器資料 +- [ ] 確認 wishes 陣列為空 +- [ ] 確認 wishLikes 物件為空 +- [ ] 確認 userLikedWishes 陣列為空 +- [ ] 確認背景音樂狀態重置 + +### 🎯 功能測試 +- [ ] 首頁許願瓶動畫正常 +- [ ] 導航選單在各裝置正常 +- [ ] 分享困擾表單提交流程完整 +- [ ] 隱私設定(公開/私密)功能正常 +- [ ] 聆聽心聲頁面顯示空狀態提示 +- [ ] 問題洞察頁面顯示無資料狀態 +- [ ] 感謝頁面正確顯示統計資訊 +- [ ] 音效控制開關正常 +- [ ] 背景音樂控制正常 + +### 📱 響應式設計 +- [ ] 手機版 (320px-768px) 顯示正常 +- [ ] 平板版 (768px-1024px) 顯示正常 +- [ ] 桌面版 (1024px+) 顯示正常 +- [ ] 導航選單在小螢幕正確收合 +- [ ] 表單在各裝置易於操作 +- [ ] 卡片佈局響應式正常 + +### 🎨 視覺效果 +- [ ] 星空背景動畫流暢 +- [ ] 許願瓶呼吸動畫正常 +- [ ] 按鈕懸停效果正常 +- [ ] 載入動畫顯示正確 +- [ ] 顏色主題一致性 +- [ ] 字體大小適中易讀 + +## 佈署後驗證 (Post-deployment) + +### 🌐 網站可用性 +- [ ] 所有頁面路由正常載入 +- [ ] 靜態資源(圖片、音效)正確載入 +- [ ] 無 404 錯誤 +- [ ] 無 JavaScript 錯誤 +- [ ] HTTPS 憑證正常(如適用) + +### ⚡ 效能檢查 +- [ ] 首頁載入時間 < 3 秒 +- [ ] 頁面切換流暢 +- [ ] 動畫不影響頁面效能 +- [ ] 音效檔案載入不阻塞頁面 +- [ ] 圖片載入優化 + +### 🔧 功能驗證 +- [ ] 提交第一個困擾案例 +- [ ] 驗證公開案例出現在聆聽心聲 +- [ ] 驗證私密案例不出現在聆聽心聲 +- [ ] 驗證分析頁面正確統計 +- [ ] 測試搜尋和篩選功能 +- [ ] 測試點讚功能 +- [ ] 測試 AI 解決方案建議 + +### 📊 資料流程 +- [ ] 案例提交成功儲存 +- [ ] 統計數字正確更新 +- [ ] 分類自動標籤正常 +- [ ] 隱私設定正確執行 +- [ ] 時間戳記正確記錄 + +## 使用者接受測試 (UAT) + +### 👤 一般使用者流程 +- [ ] 能夠輕鬆找到分享困擾入口 +- [ ] 表單填寫體驗良好 +- [ ] 隱私選項說明清楚 +- [ ] 提交後回饋明確 +- [ ] 能夠瀏覽其他人的經歷 +- [ ] 能夠表達共鳴支持 + +### 👨‍💼 管理者流程 +- [ ] 能夠查看完整問題分析 +- [ ] 統計圖表資訊有用 +- [ ] 能夠理解問題分類 +- [ ] 趨勢分析有助決策 +- [ ] 隱私保護說明清楚 + +### 🎵 多媒體體驗 +- [ ] 音效增強使用體驗 +- [ ] 背景音樂不干擾操作 +- [ ] 音量控制方便使用 +- [ ] 可以選擇關閉音效 + +## 安全性檢查 + +### 🔒 資料保護 +- [ ] 無個人識別資訊洩露 +- [ ] 私密案例確實不公開 +- [ ] 本地儲存資料加密(如需要) +- [ ] 無敏感資訊在網路傳輸 + +### 🛡️ 輸入驗證 +- [ ] 表單輸入長度限制 +- [ ] 防止 XSS 攻擊 +- [ ] 輸入內容過濾 +- [ ] 錯誤處理適當 + +## 文件和支援 + +### 📚 使用者指南 +- [ ] 功能說明清楚 +- [ ] 隱私政策明確 +- [ ] 常見問題解答 +- [ ] 聯絡方式提供 + +### 🔧 技術文件 +- [ ] 佈署說明完整 +- [ ] 故障排除指南 +- [ ] 維護建議清楚 +- [ ] 更新流程說明 + +--- + +## ✅ 最終確認 + +- [ ] 所有檢查項目已完成 +- [ ] 測試資料已完全清空 +- [ ] 正式環境運行穩定 +- [ ] 使用者可以開始使用 + +**佈署負責人簽名**: ________________ + +**佈署日期**: ________________ + +**版本號**: ________________ + +--- + +🎉 **恭喜!心願星河已準備好為團隊服務!** diff --git a/README-DEPLOYMENT.md b/README-DEPLOYMENT.md new file mode 100644 index 0000000..a94d613 --- /dev/null +++ b/README-DEPLOYMENT.md @@ -0,0 +1,122 @@ +# 心願星河 - 佈署指南 + +## 🚀 正式環境佈署準備 + +### 1. 資料清理 +在佈署前,請確保已清空所有測試資料: + +\`\`\`javascript +// 在瀏覽器控制台執行以下代碼清空測試資料 +localStorage.clear() +\`\`\` + +或者執行專用的清理腳本: +- `scripts/clear-all-data.js` - 清空所有測試資料 +- `scripts/reset-to-production.js` - 重置到正式環境狀態 + +### 2. 環境檢查清單 + +#### ✅ 功能檢查 +- [ ] 首頁載入正常,許願瓶動畫運作 +- [ ] 分享困擾頁面表單功能正常 +- [ ] 聆聽心聲頁面顯示空狀態 +- [ ] 問題洞察頁面顯示無資料狀態 +- [ ] 感謝頁面跳轉正常 +- [ ] 音效系統可正常開關 +- [ ] 背景音樂控制正常 +- [ ] 響應式設計在各裝置正常 + +#### ✅ 資料狀態檢查 +- [ ] localStorage 中無測試資料 +- [ ] 所有統計數字顯示為 0 +- [ ] 分類統計圖表顯示無資料狀態 +- [ ] 搜尋和篩選功能正常 + +#### ✅ 隱私功能檢查 +- [ ] 公開/私密選項正常運作 +- [ ] 隱私說明文字正確顯示 +- [ ] 私密案例不會出現在聆聽心聲頁面 +- [ ] 分析頁面正確統計公開和私密案例 + +### 3. 佈署步驟 + +#### 使用 Vercel 佈署 +1. 確保所有測試資料已清空 +2. 提交最新代碼到 Git 倉庫 +3. 連接 Vercel 到你的 Git 倉庫 +4. 設定環境變數(如需要) +5. 執行佈署 + +#### 使用其他平台 +1. 執行 `npm run build` 建構專案 +2. 將 `out` 或 `dist` 資料夾上傳到伺服器 +3. 確保伺服器支援 SPA 路由 +4. 設定適當的 HTTP 標頭 + +### 4. 佈署後驗證 + +#### 🔍 功能驗證 +- [ ] 訪問所有頁面確認載入正常 +- [ ] 提交一個測試困擾確認流程完整 +- [ ] 檢查響應式設計在不同裝置 +- [ ] 測試音效和背景音樂功能 +- [ ] 驗證隱私設定功能 + +#### 📊 效能檢查 +- [ ] 頁面載入速度合理 +- [ ] 動畫效果流暢 +- [ ] 音效載入不影響頁面效能 +- [ ] 圖片和資源正確載入 + +### 5. 使用者指南 + +#### 🎯 管理者功能 +- **問題洞察頁面**: 查看完整的問題分析,包含公開和私密案例 +- **分類統計**: 了解團隊面臨的主要挑戰領域 +- **趨勢分析**: 追蹤問題提交的趨勢變化 + +#### 👥 使用者功能 +- **分享困擾**: 可選擇公開或私密分享工作困擾 +- **聆聽心聲**: 查看其他人公開分享的經歷 +- **AI 建議**: 獲得針對困擾的智能解決方案建議 +- **共鳴支持**: 對相似困擾表達支持 + +### 6. 維護建議 + +#### 📈 資料監控 +- 定期檢查案例提交數量 +- 關注公開/私密案例比例 +- 監控熱門問題分類變化 + +#### 🔧 技術維護 +- 定期更新依賴套件 +- 監控頁面載入效能 +- 檢查音效資源載入狀況 + +#### 🎨 使用者體驗 +- 收集使用者回饋 +- 根據使用情況調整功能 +- 持續優化響應式設計 + +### 7. 故障排除 + +#### 常見問題 +1. **音效無法播放**: 檢查瀏覽器音頻政策,需要使用者互動後才能播放 +2. **動畫不流暢**: 檢查 CSS 動畫是否被瀏覽器支援 +3. **資料不同步**: 確認 localStorage 功能正常 +4. **響應式問題**: 檢查 Tailwind CSS 是否正確載入 + +#### 聯絡支援 +如遇到技術問題,請提供: +- 瀏覽器版本和類型 +- 錯誤訊息截圖 +- 重現步驟描述 +- 裝置和螢幕尺寸資訊 + +--- + +## 🎉 佈署完成 + +恭喜!心願星河已準備好為你的團隊提供職場困擾收集和分析服務。 + +記住:每一個真實的困擾分享都是改善工作環境的重要起點! diff --git a/app/analytics/loading.tsx b/app/analytics/loading.tsx new file mode 100644 index 0000000..f15322a --- /dev/null +++ b/app/analytics/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/app/analytics/page.tsx b/app/analytics/page.tsx new file mode 100644 index 0000000..62ca4c8 --- /dev/null +++ b/app/analytics/page.tsx @@ -0,0 +1,690 @@ +"use client" + +import { useState, useEffect } from "react" +import Link from "next/link" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { + Sparkles, + ArrowLeft, + BarChart3, + TrendingUp, + Users, + Target, + BookOpen, + ChevronDown, + ChevronUp, + TrendingDown, + Minus, + Shield, + Eye, + EyeOff, +} from "lucide-react" +import RadarChart from "@/components/radar-chart" +import HeaderMusicControl from "@/components/header-music-control" +import { categories, categorizeWishMultiple, type Wish } from "@/lib/categorization" + +interface CategoryData { + name: string + count: number + percentage: number + color: string + keywords: string[] + description?: string + difficulty?: { + level: number + stars: string + label: string + estimatedTime: string + techStack: string[] + solutionType: string + complexity: string + } +} + +interface AnalyticsData { + totalWishes: number + publicWishes: number + privateWishes: number + categories: CategoryData[] + recentTrends: { + thisWeek: number + lastWeek: number + growth: number + growthLabel: string + growthIcon: "up" | "down" | "flat" + growthColor: string + } + topKeywords: { word: string; count: number }[] +} + +export default function AnalyticsPage() { + const [wishes, setWishes] = useState([]) + const [analytics, setAnalytics] = useState(null) + const [showCategoryGuide, setShowCategoryGuide] = useState(false) + + // 分析許願內容(包含所有數據,包括私密的) + const analyzeWishes = (wishList: (Wish & { isPublic?: boolean })[]): AnalyticsData => { + const totalWishes = wishList.length + const publicWishes = wishList.filter((wish) => wish.isPublic !== false).length + const privateWishes = wishList.filter((wish) => wish.isPublic === false).length + + const categoryStats: { [key: string]: number } = {} + const keywordCount: { [key: string]: number } = {} + + // 初始化分類統計 + categories.forEach((cat) => { + categoryStats[cat.name] = 0 + }) + categoryStats["其他問題"] = 0 + + // 分析每個許願(多標籤統計)- 包含所有數據 + wishList.forEach((wish) => { + const wishCategories = categorizeWishMultiple(wish) + + wishCategories.forEach((category) => { + categoryStats[category.name]++ + + // 統計關鍵字 + if (category.keywords) { + const fullText = + `${wish.title} ${wish.currentPain} ${wish.expectedSolution} ${wish.expectedEffect}`.toLowerCase() + category.keywords.forEach((keyword: string) => { + if (fullText.includes(keyword.toLowerCase())) { + keywordCount[keyword] = (keywordCount[keyword] || 0) + 1 + } + }) + } + }) + }) + + // 計算百分比和準備數據 + const categoriesData: CategoryData[] = categories.map((cat) => ({ + name: cat.name, + count: categoryStats[cat.name] || 0, + percentage: totalWishes > 0 ? Math.round(((categoryStats[cat.name] || 0) / totalWishes) * 100) : 0, + color: cat.color, + keywords: cat.keywords, + description: cat.description, + })) + + // 改進的趨勢計算 + const now = new Date() + const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) + const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000) + + const thisWeek = wishList.filter((wish) => new Date(wish.createdAt) >= oneWeekAgo).length + const lastWeek = wishList.filter((wish) => { + const date = new Date(wish.createdAt) + return date >= twoWeeksAgo && date < oneWeekAgo + }).length + + // 改進的成長趨勢計算 + let growth = 0 + let growthLabel = "持平" + let growthIcon: "up" | "down" | "flat" = "flat" + let growthColor = "#6B7280" + + if (lastWeek === 0 && thisWeek > 0) { + // 上週沒有,本週有 → 全新開始 + growth = 100 + growthLabel = "開始增長" + growthIcon = "up" + growthColor = "#10B981" + } else if (lastWeek === 0 && thisWeek === 0) { + // 兩週都沒有 + growth = 0 + growthLabel = "尚無數據" + growthIcon = "flat" + growthColor = "#6B7280" + } else if (lastWeek > 0) { + // 正常計算成長率 + growth = Math.round(((thisWeek - lastWeek) / lastWeek) * 100) + + if (growth > 0) { + growthLabel = "持續增長" + growthIcon = "up" + growthColor = "#10B981" + } else if (growth < 0) { + growthLabel = "有所下降" + growthIcon = "down" + growthColor = "#EF4444" + } else { + growthLabel = "保持穩定" + growthIcon = "flat" + growthColor = "#6B7280" + } + } + + // 取得熱門關鍵字 + const topKeywords = Object.entries(keywordCount) + .sort(([, a], [, b]) => b - a) + .slice(0, 15) + .map(([word, count]) => ({ word, count })) + + return { + totalWishes, + publicWishes, + privateWishes, + categories: categoriesData, + recentTrends: { + thisWeek, + lastWeek, + growth, + growthLabel, + growthIcon, + growthColor, + }, + topKeywords, + } + } + + useEffect(() => { + const savedWishes = JSON.parse(localStorage.getItem("wishes") || "[]") + setWishes(savedWishes) + setAnalytics(analyzeWishes(savedWishes)) + }, []) + + if (!analytics) { + return ( +
+
正在分析數據...
+
+ ) + } + + // 根據成長趨勢選擇圖標 + const GrowthIcon = + analytics.recentTrends.growthIcon === "up" + ? TrendingUp + : analytics.recentTrends.growthIcon === "down" + ? TrendingDown + : Minus + + return ( +
+ {/* 星空背景 */} +
+ {[...Array(30)].map((_, i) => ( +
+ ))} +
+
+ + {/* Header - 修復跑版問題 */} +
+
+
+ {/* Logo 區域 - 防止文字換行 */} + +
+ +
+

心願星河

+ + + {/* 導航區域 */} + +
+
+
+ + {/* Main Content */} +
+
+ {/* 頁面標題 */} +
+
+
+ +
+

問題洞察分析

+ + 完整數據分析 + +
+

深入分析職場困擾的分布和趨勢

+

包含所有提交的案例數據,協助管理者了解真實狀況

+
+ + {/* 隱私說明卡片 */} + + + +
+ +
+ 數據隱私說明 +
+ + 本分析包含所有提交的案例,包括選擇保持私密的困擾 + +
+ +
+
+

+ + 公開案例 ({analytics.publicWishes} 個) +

+

這些案例會顯示在「聆聽心聲」頁面,供其他人查看和產生共鳴

+
+
+

+ + 私密案例 ({analytics.privateWishes} 個) +

+

這些案例保持匿名且私密,僅用於統計分析,幫助了解整體趨勢

+
+
+
+

+ 給管理者的說明: + 此分析包含完整的問題數據,能幫助您了解團隊面臨的真實挑戰。私密案例雖然不會公開顯示, + 但其數據對於制定改善策略同樣重要。所有個人身份資訊都已匿名化處理。 +

+
+
+
+ + {/* 統計概覽 */} +
+ + +
+ +
+
{analytics.totalWishes}
+
總案例數
+
+ 公開 {analytics.publicWishes} + 私密 {analytics.privateWishes} +
+
+
+ + + +
+ +
+
{analytics.recentTrends.thisWeek}
+
本週新增
+
+
+ + + +
+ +
+
+ {analytics.categories.filter((c) => c.count > 0).length} +
+
問題領域
+
+
+ + + +
+ +
+
+ {analytics.recentTrends.growth > 0 ? "+" : ""} + {analytics.recentTrends.growth}% +
+
+ {analytics.recentTrends.growthLabel} +
+ {/* 詳細說明 */} +
上週: {analytics.recentTrends.lastWeek} 個
+
+
+
+ + {/* 分類指南 */} + + +
+
+
+ +
+
+ 問題分類說明 + + 了解我們如何分類和分析各種職場困擾 + +
+
+ +
+
+ + {showCategoryGuide && ( + +
+ {categories.map((category, index) => ( +
+
+
{category.icon}
+
+
+

{category.name}

+
+
+

{category.description}

+
+
+ + {/* 關鍵字示例 */} +
+
常見關鍵字:
+
+ {category.keywords.slice(0, 6).map((keyword, idx) => ( + + {keyword} + + ))} + {category.keywords.length > 6 && ( + + +{category.keywords.length - 6} + + )} +
+
+
+ ))} +
+
+ )} +
+ + {/* 手機版:垂直佈局,桌面版:並排佈局 */} +
+ {/* 雷達圖 - 手機版給予更多高度 */} + + + +
+ +
+ 問題分布圖譜 +
+ 各類職場困擾的完整案例分布(包含私密數據) +
+ + {/* 手機版使用更大的高度,桌面版保持原有高度 */} +
+ +
+
+
+ + {/* 分類詳細統計 */} + + + +
+ +
+ 完整案例統計 + + 含私密數據 + +
+ + 每個領域的所有案例數量(包含公開和私密案例) + {analytics.categories.filter((cat) => cat.count > 0).length > 0 && ( + + 共 {analytics.categories.filter((cat) => cat.count > 0).length} 個活躍分類 + {analytics.categories.filter((cat) => cat.count > 0).length > 4 && ",可滾動查看全部"} + + )} + +
+ + {/* 設定固定高度並添加滾動 */} +
+ {analytics.categories + .filter((cat) => cat.count > 0) + .sort((a, b) => b.count - a.count) + .map((category, index) => ( +
+
+
+ {categories.find((cat) => cat.name === category.name)?.icon || "❓"} +
+
+
+ {category.name} +
+ {/* 添加排名標示 */} + {index < 3 && ( + + TOP {index + 1} + + )} +
+
{category.count} 個案例
+ {category.description && ( +
{category.description}
+ )} +
+
+ + {category.percentage}% + +
+ ))} +
+ + {/* 滾動提示 */} + {analytics.categories.filter((cat) => cat.count > 0).length > 4 && ( +
+
+
+ 向下滾動查看更多分類 +
+
+
+ )} +
+
+
+ + {/* 多維度分析說明 */} + + + +
+ +
+ 完整數據分析優勢 +
+ 包含私密案例的全面分析,提供更準確的洞察 +
+ +
+
+

🔍 真實全貌

+

包含所有案例數據,不受公開意願影響,呈現最真實的問題狀況

+
+
+

📊 精準決策

+

基於完整數據制定改善策略,避免因樣本偏差導致的決策失誤

+
+
+

🎯 隱藏問題

+

發現那些員工不願公開但確實存在的問題,提前預防和解決

+
+
+

🔒 隱私保護

+

在保護個人隱私的前提下,最大化數據的分析價值

+
+
+
+
+ + {/* 熱門關鍵字 */} + {analytics.topKeywords.length > 0 && ( + + + +
+ +
+ 最常見的問題關鍵字 +
+ + 在所有案例中最常出現的詞彙,反映團隊面臨的核心挑戰 + +
+ +
+ {analytics.topKeywords.map((keyword, index) => ( + + {keyword.word} ({keyword.count}) + + ))} +
+
+
+ )} +
+
+
+ ) +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..ac68442 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,94 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..cd8cd0e --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,31 @@ +import type React from "react" +import type { Metadata } from "next" +import { Inter } from "next/font/google" +import "./globals.css" +import { ThemeProvider } from "@/components/theme-provider" +import { Toaster } from "@/components/ui/toaster" + +const inter = Inter({ subsets: ["latin"] }) + +export const metadata: Metadata = { + title: "心願星河 - 職場困擾的專業支援平台", + description: "每一個工作困擾都值得被理解和支持。讓我們用科技的力量,為你的職場挑戰找到專業的解決方案。", + generator: 'v0.dev' +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + {children} + + + + + ) +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..8959a92 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,391 @@ +"use client" + +import Link from "next/link" +import { Button } from "@/components/ui/button" +import { Sparkles, MessageCircle, Users, BarChart3 } from "lucide-react" +import HeaderMusicControl from "@/components/header-music-control" + +export default function HomePage() { + return ( +
+ {/* 星空背景 */} +
+ {/* 星星 */} + {[...Array(30)].map((_, i) => ( +
+ ))} + + {/* 較大的星星 */} + {[...Array(15)].map((_, i) => ( +
+ ))} + + {/* 光芒效果 */} +
+
+ + {/* Header - 手機版優化,修復跑版問題 */} +
+
+
+ {/* Logo 區域 - 防止文字換行 */} +
+
+ +
+

心願星河

+
+ + {/* 導航區域 */} + +
+
+
+ + {/* Main Content - 使用 flex-1 讓內容區域填滿剩餘空間 */} +
+
+ {/* 主要許願瓶 - 添加呼吸動畫 */} +
+
+ {/* 許願瓶主體 - 呼吸動畫 */} +
+ {/* 瓶口 */} +
+ + {/* 瓶內發光效果 - 脈動動畫 */} +
+ + {/* 瓶內的月亮和人物剪影 */} +
+
+
+
+
+
+
+ {/* 小人物剪影 */} +
+
+ + {/* 瓶身光澤 */} +
+ + {/* 漂浮的光點 - 星光飄散動畫 */} +
+
+
+ + {/* 額外的星光粒子 */} +
+
+
+ + {/* 周圍的光芒 - 呼吸光暈 */} +
+
+ +

+ 心願星河 +

+

+ 每一個工作困擾都值得被理解和支持 +
+ + 讓我們用科技的力量,為你的職場挑戰找到解決方案 +

+
+ + {/* 按鈕 */} +
+ + + + + + +
+
+
+ + {/* Footer - 固定在底部 */} +
+
+
+
+ +
+ 心願星河 +
+

理解每一份職場困擾,用科技創造更好的工作環境

+
+
+ + {/* 內聯 CSS 動畫定義 */} + +
+ ) +} diff --git a/app/submit/page.tsx b/app/submit/page.tsx new file mode 100644 index 0000000..a29dc3b --- /dev/null +++ b/app/submit/page.tsx @@ -0,0 +1,541 @@ +"use client" + +import type React from "react" + +import { useState, useEffect } from "react" +import Link from "next/link" +import { useRouter } from "next/navigation" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Textarea } from "@/components/ui/textarea" +import { Checkbox } from "@/components/ui/checkbox" +import { Sparkles, ArrowLeft, Send, BarChart3, Eye, EyeOff, Shield, Info } from "lucide-react" +import { useToast } from "@/hooks/use-toast" +import { soundManager } from "@/lib/sound-effects" +import HeaderMusicControl from "@/components/header-music-control" + +export default function SubmitPage() { + const [formData, setFormData] = useState({ + title: "", + currentPain: "", + expectedSolution: "", + expectedEffect: "", + isPublic: true, // 預設為公開 + }) + const [isSubmitting, setIsSubmitting] = useState(false) + const { toast } = useToast() + const router = useRouter() + + // 初始化音效系統 + useEffect(() => { + const initSound = async () => { + // 用戶首次點擊時啟動音頻上下文 + const handleFirstInteraction = async () => { + await soundManager.play("hover") // 測試音效 + document.removeEventListener("click", handleFirstInteraction) + } + document.addEventListener("click", handleFirstInteraction) + } + initSound() + }, []) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setIsSubmitting(true) + + // 播放提交音效 + await soundManager.play("submit") + + await new Promise((resolve) => setTimeout(resolve, 1500)) + + const wishes = JSON.parse(localStorage.getItem("wishes") || "[]") + const newWish = { + id: Date.now(), + ...formData, + createdAt: new Date().toISOString(), + } + wishes.push(newWish) + localStorage.setItem("wishes", JSON.stringify(wishes)) + + // 播放成功音效 + await soundManager.play("success") + + toast({ + title: "你的困擾已成功提交", + description: formData.isPublic + ? "正在為你準備專業的回饋,其他人也能看到你的分享..." + : "正在為你準備專業的回饋,你的分享將保持私密...", + }) + + setFormData({ + title: "", + currentPain: "", + expectedSolution: "", + expectedEffect: "", + isPublic: true, + }) + setIsSubmitting(false) + + // 跳轉到感謝頁面 + setTimeout(() => { + router.push("/thank-you") + }, 1000) + } + + const handleChange = (field: string, value: string | boolean) => { + setFormData((prev) => ({ ...prev, [field]: value })) + } + + const handleButtonClick = async () => { + await soundManager.play("click") + } + + return ( +
+ {/* 星空背景 - 手機優化 */} +
+ {[...Array(25)].map((_, i) => ( +
+ ))} +
+
+ + {/* Header - 修復跑版問題 */} +
+
+
+ {/* Logo 區域 - 防止文字換行 */} + +
+ +
+

心願星河

+ + + {/* 導航區域 */} + +
+
+
+ + {/* Main Content - 手機優化 */} +
+
+
+ {/* 小許願瓶 - 添加呼吸動畫 */} +
+
+
+
+
+
+ + {/* 小星光粒子 */} +
+
+
+
+
+ + {/* 呼吸光暈 */} +
+
+
+ +

分享你的工作困擾

+

+ 每一個困擾都是改善的起點,我們會用專業的角度為你分析和建議 +

+
+ + + + +
+ +
+ 困擾分享 +
+ + 請詳細描述你的工作困擾,我們會認真分析並提供專業建議 + +
+ +
+
+ + handleChange("title", e.target.value)} + required + className="bg-slate-700/50 border-blue-600/50 text-white placeholder:text-blue-300 focus:border-cyan-400 text-sm md:text-base" + /> +
+ +
+ +