-
Notifications
You must be signed in to change notification settings - Fork 15
Add demo seed data and improve insights (Weekly Summary + HeatMap) #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4efcd6c
d2d29ff
7d08d70
8a9a250
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,69 +1,56 @@ | ||
| // src/App.js | ||
| import './App.css'; | ||
|
|
||
| // router | ||
| import { Routes, Route, useLocation, Navigate } from 'react-router-dom'; | ||
|
|
||
| // framer | ||
| import { AnimatePresence } from 'framer-motion'; | ||
|
|
||
| // stores | ||
| import { useDialogStore } from './stores/dialogStore'; | ||
|
|
||
| // main components | ||
| import MainPage from './components/MainPage'; | ||
| import Modal from './components/Modal'; | ||
| import Dialog from './components/Containment/Dialog'; | ||
|
|
||
| // hooks | ||
| import useColorScheme from './hooks/useColorScheme'; | ||
| import useAchievementsCheck from './hooks/useAchievementsCheck'; | ||
|
|
||
| // db | ||
| import dbModalRoutes from './db/dbModalRoutes'; | ||
|
|
||
| // NEW COMPONENTS | ||
| import WeeklySummary from './components/WeeklySummary'; | ||
| import HeatMap from './components/HeatMap'; | ||
|
|
||
| const PUBLIC_URL = process.env.PUBLIC_URL; | ||
|
|
||
| function App() { | ||
|
|
||
| const location = useLocation(); | ||
| const isDialogVisible = useDialogStore((s) => s.isVisible); | ||
|
|
||
| // Get colors from database based on settings or system theme | ||
| useColorScheme(); | ||
|
|
||
| // Check achievements when dependencies change | ||
| useAchievementsCheck(); | ||
|
|
||
| return ( | ||
| <main className="App"> | ||
| <AnimatePresence initial={false}> | ||
| <Routes location={location} key={location.pathname}> | ||
| <Route | ||
| path='*' | ||
| element={<Navigate to={PUBLIC_URL} />} | ||
| /> | ||
|
|
||
| <Route | ||
| path={PUBLIC_URL} | ||
| element={<MainPage />} | ||
| /> | ||
|
|
||
| <Route | ||
| path={`${PUBLIC_URL}/modal`} | ||
| element={<Modal />} | ||
| > | ||
| {dbModalRoutes.map((r) => ( | ||
| <Route key={r.path} path={r.path} element={r.element} /> | ||
| ))} | ||
| </Route> | ||
| </Routes> | ||
|
|
||
| {isDialogVisible && ( | ||
| <Dialog key="dialog" /> | ||
| )} | ||
| </AnimatePresence> | ||
| </main> | ||
| ); | ||
| const location = useLocation(); | ||
| const isDialogVisible = useDialogStore((s) => s.isVisible); | ||
|
|
||
| useColorScheme(); | ||
| useAchievementsCheck(); | ||
|
|
||
| return ( | ||
| <main className="App"> | ||
| <AnimatePresence initial={false}> | ||
| <Routes location={location} key={location.pathname}> | ||
| <Route path="*" element={<Navigate to={PUBLIC_URL} />} /> | ||
|
|
||
| <Route | ||
| path={PUBLIC_URL} | ||
| element={ | ||
| <> | ||
| <WeeklySummary /> | ||
| <HeatMap /> | ||
| <MainPage /> | ||
| </> | ||
| } | ||
| /> | ||
|
|
||
| <Route path={`${PUBLIC_URL}/modal`} element={<Modal />}> | ||
| {dbModalRoutes.map((r) => ( | ||
| <Route key={r.path} path={r.path} element={r.element} /> | ||
| ))} | ||
| </Route> | ||
| </Routes> | ||
|
|
||
| {isDialogVisible && <Dialog key="dialog" />} | ||
| </AnimatePresence> | ||
| </main> | ||
| ); | ||
| } | ||
|
|
||
| export default App; | ||
| export default App; |
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please redo the styles to match our |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| /* src/components/Heatmap.css */ | ||
| .hm-wrapper { | ||
| border: 1px solid #e6e6e6; | ||
| padding: 12px; | ||
| border-radius: 8px; | ||
| background: #fff; | ||
| max-width: 720px; | ||
| margin-bottom: 12px; | ||
| }/* src/components/HeatMap.css */ | ||
| .hm-wrapper { | ||
| border: 1px solid #e6e6e6; | ||
| padding: 12px; | ||
| border-radius: 8px; | ||
| background: #fff; | ||
| max-width: 720px; | ||
| margin-bottom: 12px; | ||
| } | ||
|
|
||
| .hm-header { | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| gap: 12px; | ||
| margin-bottom: 8px; | ||
| } | ||
|
|
||
| .hm-header h4 { | ||
| margin: 0; | ||
| font-size: 14px; | ||
| } | ||
|
|
||
| .hm-legend { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 8px; | ||
| font-size: 12px; | ||
| color: #444; | ||
| } | ||
|
|
||
| .legend-dot { | ||
| display: inline-block; | ||
| width: 12px; | ||
| height: 12px; | ||
| border-radius: 2px; | ||
| margin-right: 4px; | ||
| vertical-align: middle; | ||
| } | ||
| .legend-dot.none { | ||
| background: #f0f0f0; | ||
| border: 1px solid #e0e0e0; | ||
| } | ||
| .legend-dot.light { | ||
| background: #d6f5d6; | ||
| } | ||
| .legend-dot.medium { | ||
| background: #66d966; | ||
| } | ||
| .legend-dot.strong { | ||
| background: #238823; | ||
| } | ||
|
|
||
| .hm-grid { | ||
| display: flex; | ||
| gap: 6px; | ||
| align-items: stretch; | ||
| overflow-x: auto; | ||
| padding-bottom: 6px; | ||
| } | ||
|
|
||
| /* one horizontal row for now */ | ||
| .hm-row { | ||
| display: flex; | ||
| flex-direction: row; | ||
| gap: 6px; | ||
| } | ||
|
|
||
| /* cell base */ | ||
| .hm-cell { | ||
| width: 14px; | ||
| height: 14px; | ||
| border-radius: 2px; | ||
| box-sizing: border-box; | ||
| border: 1px solid transparent; | ||
| } | ||
| .hm-cell.none { | ||
| background: #f0f0f0; | ||
| border: 1px solid #eaeaea; | ||
| } | ||
| .hm-cell.light { | ||
| background: #d6f5d6; | ||
| } | ||
| .hm-cell.medium { | ||
| background: #66d966; | ||
| } | ||
| .hm-cell.strong { | ||
| background: #238823; | ||
| } | ||
|
|
||
| @media (max-width: 520px) { | ||
| .hm-cell { | ||
| width: 12px; | ||
| height: 12px; | ||
| } | ||
| .hm-wrapper { | ||
| padding: 10px; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| .hm-header { | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| gap: 12px; | ||
| margin-bottom: 8px; | ||
| } | ||
|
|
||
| .hm-header h4 { | ||
| margin: 0; | ||
| font-size: 14px; | ||
| } | ||
|
|
||
| .hm-legend { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 8px; | ||
| font-size: 12px; | ||
| color: #444; | ||
| } | ||
|
|
||
| .legend-dot { | ||
| display: inline-block; | ||
| width: 12px; | ||
| height: 12px; | ||
| border-radius: 2px; | ||
| margin-right: 4px; | ||
| vertical-align: middle; | ||
| } | ||
| .legend-dot.none { background: #f0f0f0; border: 1px solid #e0e0e0; } | ||
| .legend-dot.light { background: #d6f5d6; } | ||
| .legend-dot.medium { background: #66d966; } | ||
| .legend-dot.strong { background: #238823; } | ||
|
|
||
| .hm-grid { | ||
| display: flex; | ||
| gap: 6px; | ||
| align-items: stretch; | ||
| overflow-x: auto; | ||
| padding-bottom: 6px; | ||
| } | ||
|
|
||
| /* each column is a vertical stack for weekdays */ | ||
| .hm-row { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 6px; | ||
| } | ||
|
|
||
| /* cell base */ | ||
| .hm-cell { | ||
| width: 14px; | ||
| height: 14px; | ||
| border-radius: 2px; | ||
| box-sizing: border-box; | ||
| border: 1px solid transparent; | ||
| } | ||
| .hm-cell.none { | ||
| background: #f0f0f0; | ||
| border: 1px solid #eaeaea; | ||
| } | ||
| .hm-cell.light { | ||
| background: #d6f5d6; | ||
| } | ||
| .hm-cell.medium { | ||
| background: #66d966; | ||
| } | ||
| .hm-cell.strong { | ||
| background: #238823; | ||
| } | ||
|
|
||
| /* responsive tweaks */ | ||
| @media (max-width: 520px) { | ||
| .hm-cell { width: 12px; height: 12px; } | ||
| .hm-wrapper { padding: 10px; } | ||
| } |
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Logic: Your current calculation is wrong. If I have 3 habits with frequency 2, and I do each one once, I finished 0 habits. But your map shows "3" and marks me as a hero. We should count only fully completed habits (where progress >= frequency). Calculation: Using a percentage (ratio) is also problematic because archiving or adding new habits will break the old history. Let's just count the absolute number of completed habits per day and use that for the levels (e.g., 1, 2, 3+). If you disagree, explain your point of view. UI/UX: Did you test this in the browser?
ps: I left other comments in the file. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // src/components/HeatMap.js | ||
|
|
||
| import { useHabitsStore } from "../stores/habitsStore"; | ||
| import { getPastDays } from "../utils/dateUtils"; | ||
| import "./HeatMap.css"; | ||
|
|
||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stop summing raw progress. We only count a habit as "done" if const DAYS_COUNT = 30;
const days = getPastDays(DAYS_COUNT);
export default function HeatMap() {
const habits = useHabitsStore((s) => s.habits ?? []);
const activity = {}
habits.forEach((habit) => {
habit.completedDays?.forEach((d, i) => {
// Only check the latest 30 entries
if (i >= DAYS_COUNT) return;
const isCompleted = d.progress >= habit.frequency;
if (isCompleted) {
activity[d.date] = (activity[d.date] || 0) + 1;
}
});
});
...
} |
||
| export default function HeatMap() { | ||
| const habits = useHabitsStore((s) => s.habits || []); | ||
|
|
||
| const days = getPastDays(30); | ||
|
|
||
| const activity = {}; | ||
|
|
||
| habits.forEach((habit) => { | ||
| habit.completedDays?.forEach((d) => { | ||
| activity[d.date] = (activity[d.date] || 0) + (d.progress || 0); | ||
| }); | ||
| }); | ||
|
|
||
| return ( | ||
| <div className="hm-wrapper"> | ||
| <div className="hm-header"> | ||
| <h4>Activity — last 30 days</h4> | ||
| </div> | ||
|
|
||
| <div className="hm-grid"> | ||
| <div className="hm-row"> | ||
| {days.map((d) => { | ||
| const value = activity[d] || 0; | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UI: Fixed the cell classes to use level-X format. Please fix the CSS: the grid should be horizontal, support dark mode, and remove the unnecessary borders. const completedCount = activity[d] ?? 0;
let level = 0;
if (completedCount === 1) level = 1;
else if (completedCount === 2) level = 2;
else if (completedCount >= 3) level = 3;
return <div key={d} className={`hm-cell level-${level}`} />; |
||
|
|
||
| let cls = "hm-cell none"; | ||
|
|
||
| if (value === 1) cls = "hm-cell light"; | ||
| if (value === 2) cls = "hm-cell medium"; | ||
| if (value >= 3) cls = "hm-cell strong"; | ||
|
|
||
| return <div key={d} className={cls} />; | ||
| })} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WeeklySummaryandHeatMapbelong inMainPage, not here in App routes. Please move them there. Also, if you start the app from scratch with no habits, your components appear on top of the "create first habit" banner. Please fix this bug.ps: Please don't change formatting (tabs and empty lines) because it adds unnecessary noise to the PR.