Add QuitPlanCard with 7-day tracking and personalized quit plan
- Restore QuitPlanCard component under calendar on dashboard - Yellow gradient during tracking phase (before 7 days of data) - Pink gradient when quit plan is active - Track unique days with logged data for countdown - Generate 4-week plan with 25% weekly reduction - White text throughout for readability Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
26e64ac572
commit
ec0d83586d
@ -8,6 +8,7 @@ import {
|
||||
savePreferencesAsync,
|
||||
saveUsageEntryAsync,
|
||||
shouldShowUsagePrompt,
|
||||
generateQuitPlan,
|
||||
UserPreferences,
|
||||
UsageEntry,
|
||||
} from '@/lib/storage';
|
||||
@ -16,6 +17,7 @@ import { SetupWizard } from './SetupWizard';
|
||||
import { UsagePromptDialog } from './UsagePromptDialog';
|
||||
import { UsageCalendar } from './UsageCalendar';
|
||||
import { StatsCard } from './StatsCard';
|
||||
import { QuitPlanCard } from './QuitPlanCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { PlusCircle } from 'lucide-react';
|
||||
|
||||
@ -98,6 +100,19 @@ export function Dashboard({ user }: DashboardProps) {
|
||||
setRefreshKey(prev => prev + 1);
|
||||
};
|
||||
|
||||
const handleGeneratePlan = async () => {
|
||||
if (!preferences) return;
|
||||
|
||||
const plan = generateQuitPlan(preferences.substance);
|
||||
const updatedPrefs = {
|
||||
...preferences,
|
||||
quitPlan: plan,
|
||||
};
|
||||
await savePreferencesAsync(updatedPrefs);
|
||||
setPreferences(updatedPrefs);
|
||||
setRefreshKey(prev => prev + 1);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
@ -133,6 +148,12 @@ export function Dashboard({ user }: DashboardProps) {
|
||||
onDataUpdate={loadData}
|
||||
userId={user.id}
|
||||
/>
|
||||
<QuitPlanCard
|
||||
key={`quit-plan-${refreshKey}`}
|
||||
plan={preferences.quitPlan}
|
||||
onGeneratePlan={handleGeneratePlan}
|
||||
usageData={usageData}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<StatsCard key={`stats-nicotine-${refreshKey}`} usageData={usageData} substance="nicotine" />
|
||||
|
||||
@ -2,62 +2,82 @@
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { QuitPlan, getCurrentWeekTarget } from '@/lib/storage';
|
||||
import { QuitPlan, UsageEntry } from '@/lib/storage';
|
||||
import { Target, TrendingDown } from 'lucide-react';
|
||||
|
||||
interface QuitPlanCardProps {
|
||||
plan: QuitPlan | null;
|
||||
onGeneratePlan: () => void;
|
||||
hasEnoughData: boolean;
|
||||
daysTracked: number;
|
||||
currentAverage: number;
|
||||
usageData: UsageEntry[];
|
||||
}
|
||||
|
||||
export function QuitPlanCard({
|
||||
plan,
|
||||
onGeneratePlan,
|
||||
hasEnoughData,
|
||||
daysTracked,
|
||||
currentAverage,
|
||||
usageData,
|
||||
}: QuitPlanCardProps) {
|
||||
const currentTarget = getCurrentWeekTarget();
|
||||
// Count unique days with any logged data
|
||||
const uniqueDaysWithData = new Set(usageData.map(e => e.date)).size;
|
||||
const daysRemaining = Math.max(0, 7 - uniqueDaysWithData);
|
||||
const hasEnoughData = uniqueDaysWithData >= 7;
|
||||
|
||||
// Calculate current average
|
||||
const totalUsage = usageData.reduce((sum, e) => sum + e.count, 0);
|
||||
const currentAverage = uniqueDaysWithData > 0 ? Math.round(totalUsage / uniqueDaysWithData) : 0;
|
||||
|
||||
if (!plan) {
|
||||
return (
|
||||
<Card className="bg-card/80 backdrop-blur-sm">
|
||||
<Card className="backdrop-blur-sm shadow-lg drop-shadow-md border-yellow-500/30" style={{
|
||||
background: 'linear-gradient(135deg, rgba(234, 179, 8, 0.2) 0%, rgba(202, 138, 4, 0.15) 100%)'
|
||||
}}>
|
||||
<CardHeader>
|
||||
<CardTitle>Your Quit Plan</CardTitle>
|
||||
<CardDescription>
|
||||
Track your usage for 7 days to receive a personalized quit plan
|
||||
<CardTitle className="flex items-center gap-2 text-white">
|
||||
<Target className="h-5 w-5 text-yellow-400" />
|
||||
Your Personalized Plan
|
||||
</CardTitle>
|
||||
<CardDescription className="text-white/70">
|
||||
We're tracking your usage to build your custom quit plan
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="bg-muted p-4 rounded-lg">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-sm font-medium">Tracking Progress</span>
|
||||
<span className="text-sm text-muted-foreground">{daysTracked}/7 days</span>
|
||||
<div className="bg-yellow-500/20 border border-yellow-500/30 p-4 rounded-lg">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<span className="text-sm font-medium text-white">Tracking Progress</span>
|
||||
<span className="text-sm text-yellow-300 font-bold">
|
||||
{daysRemaining > 0 ? `${daysRemaining} days left` : 'Ready!'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-background rounded-full h-2">
|
||||
<div className="w-full bg-white/10 rounded-full h-3">
|
||||
<div
|
||||
className="bg-primary h-2 rounded-full transition-all"
|
||||
style={{ width: `${Math.min(100, (daysTracked / 7) * 100)}%` }}
|
||||
className="bg-gradient-to-r from-yellow-400 to-yellow-500 h-3 rounded-full transition-all"
|
||||
style={{ width: `${Math.min(100, (uniqueDaysWithData / 7) * 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-white/60 mt-2 text-center">
|
||||
{uniqueDaysWithData} of 7 days tracked
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{hasEnoughData ? (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
You've tracked for a week. Your average daily usage is{' '}
|
||||
<strong>{currentAverage}</strong> times per day.
|
||||
<p className="text-sm text-white text-center">
|
||||
Great work! Your average daily usage is{' '}
|
||||
<strong className="text-yellow-300">{currentAverage}</strong> per day.
|
||||
</p>
|
||||
<Button onClick={onGeneratePlan} className="w-full">
|
||||
<Button onClick={onGeneratePlan} className="w-full bg-yellow-500 hover:bg-yellow-600 text-black font-semibold">
|
||||
<TrendingDown className="mr-2 h-4 w-4" />
|
||||
Generate My Quit Plan
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Keep tracking! We need at least 7 days of data to create your personalized plan.
|
||||
</p>
|
||||
<div className="text-center space-y-2">
|
||||
<p className="text-sm text-white">
|
||||
Log your usage each day. After 7 days, we'll create a personalized plan to help you reduce by <strong className="text-yellow-300">25% each week</strong>.
|
||||
</p>
|
||||
<p className="text-xs text-white/60">
|
||||
Your plan will be tailored to your habits
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -70,50 +90,57 @@ export function QuitPlanCard({
|
||||
(today.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24 * 7)
|
||||
) + 1;
|
||||
const totalWeeks = plan.weeklyTargets.length;
|
||||
const currentTarget = weekNumber <= totalWeeks ? plan.weeklyTargets[weekNumber - 1] : 0;
|
||||
|
||||
return (
|
||||
<Card className="bg-card/80 backdrop-blur-sm">
|
||||
<Card className="backdrop-blur-sm shadow-lg drop-shadow-md border-pink-500/30" style={{
|
||||
background: 'linear-gradient(135deg, rgba(236, 72, 153, 0.2) 0%, rgba(219, 39, 119, 0.15) 100%)'
|
||||
}}>
|
||||
<CardHeader>
|
||||
<CardTitle>Your Quit Plan</CardTitle>
|
||||
<CardDescription>
|
||||
Week {Math.min(weekNumber, totalWeeks)} of {totalWeeks}
|
||||
<CardTitle className="flex items-center gap-2 text-white">
|
||||
<TrendingDown className="h-5 w-5 text-pink-400" />
|
||||
Your Quit Plan
|
||||
</CardTitle>
|
||||
<CardDescription className="text-white/70">
|
||||
Week {Math.min(weekNumber, totalWeeks)} of {totalWeeks} - 25% weekly reduction
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="bg-primary/10 p-4 rounded-lg">
|
||||
<p className="text-sm text-muted-foreground mb-1">This week's target</p>
|
||||
<p className="text-3xl font-bold text-primary">
|
||||
{currentTarget !== null ? `${currentTarget} per day` : 'Complete!'}
|
||||
<div className="bg-pink-500/20 border border-pink-500/30 p-4 rounded-lg text-center">
|
||||
<p className="text-sm text-white/70 mb-1">This week's daily target</p>
|
||||
<p className="text-4xl font-bold text-pink-300">
|
||||
{currentTarget !== null && currentTarget > 0 ? currentTarget : '0'}
|
||||
</p>
|
||||
<p className="text-sm text-white/60">per day</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">Weekly reduction plan:</p>
|
||||
<div className="grid grid-cols-6 gap-2">
|
||||
<p className="text-sm font-medium text-white">Weekly targets:</p>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{plan.weeklyTargets.map((target, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`text-center p-2 rounded ${
|
||||
className={`text-center p-2 rounded-lg ${
|
||||
index + 1 === weekNumber
|
||||
? 'bg-primary text-primary-foreground'
|
||||
? 'bg-pink-500 text-white'
|
||||
: index + 1 < weekNumber
|
||||
? 'bg-green-100 dark:bg-green-900/30 text-green-900 dark:text-green-100'
|
||||
: 'bg-muted'
|
||||
? 'bg-pink-900/50 text-pink-200'
|
||||
: 'bg-white/10 text-white/60'
|
||||
}`}
|
||||
>
|
||||
<p className="text-xs">W{index + 1}</p>
|
||||
<p className="text-xs">Week {index + 1}</p>
|
||||
<p className="font-bold">{target}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div className="text-sm text-white/70 bg-white/5 p-3 rounded-lg">
|
||||
<p>
|
||||
<strong>Baseline:</strong> {plan.baselineAverage} uses/day
|
||||
<strong className="text-white">Started at:</strong> {plan.baselineAverage}/day
|
||||
</p>
|
||||
<p>
|
||||
<strong>End date:</strong> {new Date(plan.endDate).toLocaleDateString()}
|
||||
<strong className="text-white">Goal:</strong> Quit by {new Date(plan.endDate).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@ -231,15 +231,15 @@ export function generateQuitPlan(substance: 'nicotine' | 'weed', _userId?: strin
|
||||
const today = new Date();
|
||||
const startDate = today.toISOString().split('T')[0];
|
||||
|
||||
// 6-week reduction plan
|
||||
// 4-week reduction plan with 25% weekly reduction
|
||||
const endDate = new Date(today);
|
||||
endDate.setDate(endDate.getDate() + 42);
|
||||
endDate.setDate(endDate.getDate() + 28);
|
||||
|
||||
// Gradual reduction: each week reduce by ~15-20%
|
||||
// Gradual reduction: each week reduce by 25%
|
||||
const weeklyTargets: number[] = [];
|
||||
let current = baseline;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
current = Math.max(0, Math.round(current * 0.8));
|
||||
for (let i = 0; i < 4; i++) {
|
||||
current = Math.max(0, Math.round(current * 0.75));
|
||||
weeklyTargets.push(current);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user