11
22import React , { useState , useEffect } from 'react' ;
33import { Calendar } from '@/components/ui/calendar' ;
4- import { getRelapseCalendarData , getRelapseData } from '../utils/firebase' ;
4+ import { getRelapseCalendarData , getRelapseData , updateCalendarDay } from '../utils/firebase' ;
55import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from '@/components/ui/tooltip' ;
66import { Card } from '@/components/ui/card' ;
77import { isSameDay , differenceInDays } from 'date-fns' ;
88import { motion } from 'framer-motion' ;
99import { DayContentProps } from 'react-day-picker' ;
10+ import {
11+ Dialog ,
12+ DialogContent ,
13+ DialogHeader ,
14+ DialogTitle ,
15+ DialogFooter ,
16+ } from '@/components/ui/dialog' ;
17+ import { Button } from '@/components/ui/button' ;
18+ import { Label } from '@/components/ui/label' ;
19+ import { Input } from '@/components/ui/input' ;
20+ import { Textarea } from '@/components/ui/textarea' ;
21+ import { toast } from 'sonner' ;
1022
1123interface RelapseCalendarProps {
1224 userId ?: string ;
1325 showStats ?: boolean ;
26+ editable ?: boolean ;
1427}
1528
1629interface DayInfo {
@@ -22,42 +35,87 @@ interface DayInfo {
2235 } | null ;
2336}
2437
25- const RelapseCalendar : React . FC < RelapseCalendarProps > = ( { userId, showStats = false } ) => {
38+ const RelapseCalendar : React . FC < RelapseCalendarProps > = ( { userId, showStats = false , editable = false } ) => {
2639 const [ calendarData , setCalendarData ] = useState < DayInfo [ ] > ( [ ] ) ;
2740 const [ isLoading , setIsLoading ] = useState ( true ) ;
2841 const [ month , setMonth ] = useState < Date > ( new Date ( ) ) ;
2942 const [ stats , setStats ] = useState ( { cleanDays : 0 , relapseDays : 0 , netGrowth : 0 } ) ;
3043
31- useEffect ( ( ) => {
32- const fetchData = async ( ) => {
33- if ( ! userId ) {
34- setIsLoading ( false ) ;
35- return ;
36- }
44+ // Edit dialog state
45+ const [ editDialogOpen , setEditDialogOpen ] = useState ( false ) ;
46+ const [ selectedDay , setSelectedDay ] = useState < Date | null > ( null ) ;
47+ const [ selectedDayData , setSelectedDayData ] = useState < DayInfo | null > ( null ) ;
48+ const [ editMarkAsRelapse , setEditMarkAsRelapse ] = useState ( false ) ;
49+ const [ editTriggers , setEditTriggers ] = useState ( '' ) ;
50+ const [ editNotes , setEditNotes ] = useState ( '' ) ;
51+ const [ isSaving , setIsSaving ] = useState ( false ) ;
3752
38- try {
39- setIsLoading ( true ) ;
40- // Get calendar visualization data
41- const data = await getRelapseCalendarData ( userId ) ;
42- setCalendarData ( data ) ;
43-
44- // Get analytics data for accurate stats
45- const relapseData = await getRelapseData ( userId , 'all' ) ;
46- setStats ( {
47- cleanDays : relapseData . cleanDays ,
48- relapseDays : relapseData . relapseDays ,
49- netGrowth : relapseData . netGrowth
50- } ) ;
51- } catch ( error ) {
52- console . error ( 'Error fetching calendar data:' , error ) ;
53- } finally {
54- setIsLoading ( false ) ;
55- }
56- } ;
53+ const fetchData = async ( ) => {
54+ if ( ! userId ) {
55+ setIsLoading ( false ) ;
56+ return ;
57+ }
58+
59+ try {
60+ setIsLoading ( true ) ;
61+ // Get calendar visualization data
62+ const data = await getRelapseCalendarData ( userId ) ;
63+ setCalendarData ( data ) ;
64+
65+ // Get analytics data for accurate stats
66+ const relapseData = await getRelapseData ( userId , 'all' ) ;
67+ setStats ( {
68+ cleanDays : relapseData . cleanDays ,
69+ relapseDays : relapseData . relapseDays ,
70+ netGrowth : relapseData . netGrowth
71+ } ) ;
72+ } catch ( error ) {
73+ console . error ( 'Error fetching calendar data:' , error ) ;
74+ } finally {
75+ setIsLoading ( false ) ;
76+ }
77+ } ;
5778
79+ useEffect ( ( ) => {
5880 fetchData ( ) ;
5981 } , [ userId ] ) ;
6082
83+ const handleDayClick = ( day : Date ) => {
84+ if ( ! editable || ! userId ) return ;
85+ const dayData = calendarData . find ( d => isSameDay ( d . date , day ) ) || null ;
86+ setSelectedDay ( day ) ;
87+ setSelectedDayData ( dayData ) ;
88+ setEditMarkAsRelapse ( dayData ?. hadRelapse ?? false ) ;
89+ setEditTriggers ( dayData ?. relapseInfo ?. triggers ?? '' ) ;
90+ setEditNotes ( dayData ?. relapseInfo ?. notes ?? '' ) ;
91+ setEditDialogOpen ( true ) ;
92+ } ;
93+
94+ const handleSaveEdit = async ( ) => {
95+ if ( ! userId || ! selectedDay ) return ;
96+ setIsSaving ( true ) ;
97+ try {
98+ const success = await updateCalendarDay (
99+ userId ,
100+ selectedDay ,
101+ editMarkAsRelapse ,
102+ editMarkAsRelapse ? editTriggers : undefined ,
103+ editMarkAsRelapse ? editNotes : undefined
104+ ) ;
105+ if ( success ) {
106+ toast . success ( editMarkAsRelapse ? 'Day marked as relapse' : 'Day marked as clean' ) ;
107+ setEditDialogOpen ( false ) ;
108+ await fetchData ( ) ;
109+ } else {
110+ toast . error ( 'Failed to update day' ) ;
111+ }
112+ } catch ( error ) {
113+ toast . error ( 'Failed to update day' ) ;
114+ } finally {
115+ setIsSaving ( false ) ;
116+ }
117+ } ;
118+
61119 // Custom day rendering with dots for relapse status
62120 const renderDay = ( props : DayContentProps ) => {
63121 const day = props . date ;
@@ -72,7 +130,10 @@ const RelapseCalendar: React.FC<RelapseCalendarProps> = ({ userId, showStats = f
72130 return (
73131 < Tooltip >
74132 < TooltipTrigger asChild >
75- < div className = "relative w-full h-full flex items-center justify-center" >
133+ < div
134+ className = { `relative w-full h-full flex items-center justify-center ${ editable ? 'cursor-pointer' : '' } ` }
135+ onClick = { editable ? ( ) => handleDayClick ( day ) : undefined }
136+ >
76137 < div className = "w-7 h-7 flex items-center justify-center" >
77138 { day . getDate ( ) }
78139 </ div >
@@ -93,6 +154,7 @@ const RelapseCalendar: React.FC<RelapseCalendarProps> = ({ userId, showStats = f
93154 ) : (
94155 < p className = "text-green-500" > Clean day</ p >
95156 ) }
157+ { editable && < p className = "text-xs text-muted-foreground mt-1" > Click to edit</ p > }
96158 </ div >
97159 </ TooltipContent >
98160 </ Tooltip >
@@ -120,6 +182,9 @@ const RelapseCalendar: React.FC<RelapseCalendarProps> = ({ userId, showStats = f
120182 </ div >
121183 ) : (
122184 < Card className = "p-4 w-full" >
185+ { editable && (
186+ < p className = "text-xs text-muted-foreground mb-2 text-center" > Click any day to mark it as clean or relapse</ p >
187+ ) }
123188 < Calendar
124189 mode = "default"
125190 month = { month }
@@ -155,6 +220,63 @@ const RelapseCalendar: React.FC<RelapseCalendarProps> = ({ userId, showStats = f
155220 </ div >
156221 </ Card >
157222 ) }
223+
224+ { /* Edit day dialog */ }
225+ < Dialog open = { editDialogOpen } onOpenChange = { setEditDialogOpen } >
226+ < DialogContent >
227+ < DialogHeader >
228+ < DialogTitle >
229+ Edit Day – { selectedDay ? new Intl . DateTimeFormat ( 'en-US' , { month : 'long' , day : 'numeric' , year : 'numeric' } ) . format ( selectedDay ) : '' }
230+ </ DialogTitle >
231+ </ DialogHeader >
232+ < div className = "space-y-4 py-2" >
233+ < div className = "flex gap-3" >
234+ < Button
235+ variant = { editMarkAsRelapse ? 'outline' : 'default' }
236+ className = { ! editMarkAsRelapse ? 'bg-green-600 hover:bg-green-700 text-white' : '' }
237+ onClick = { ( ) => setEditMarkAsRelapse ( false ) }
238+ >
239+ Clean Day
240+ </ Button >
241+ < Button
242+ variant = { editMarkAsRelapse ? 'destructive' : 'outline' }
243+ onClick = { ( ) => setEditMarkAsRelapse ( true ) }
244+ >
245+ Relapse Day
246+ </ Button >
247+ </ div >
248+
249+ { editMarkAsRelapse && (
250+ < >
251+ < div className = "space-y-1" >
252+ < Label htmlFor = "edit-triggers" > Triggers</ Label >
253+ < Input
254+ id = "edit-triggers"
255+ placeholder = "What triggered this?"
256+ value = { editTriggers }
257+ onChange = { e => setEditTriggers ( e . target . value ) }
258+ />
259+ </ div >
260+ < div className = "space-y-1" >
261+ < Label htmlFor = "edit-notes" > Notes (optional)</ Label >
262+ < Textarea
263+ id = "edit-notes"
264+ placeholder = "Any additional notes..."
265+ value = { editNotes }
266+ onChange = { e => setEditNotes ( e . target . value ) }
267+ />
268+ </ div >
269+ </ >
270+ ) }
271+ </ div >
272+ < DialogFooter >
273+ < Button variant = "outline" onClick = { ( ) => setEditDialogOpen ( false ) } > Cancel</ Button >
274+ < Button onClick = { handleSaveEdit } disabled = { isSaving } >
275+ { isSaving ? 'Saving...' : 'Save' }
276+ </ Button >
277+ </ DialogFooter >
278+ </ DialogContent >
279+ </ Dialog >
158280 </ div >
159281 </ TooltipProvider >
160282 ) ;
0 commit comments