1- import React , { useState , useMemo } from 'react' ;
1+ import React , { useState , useMemo , useEffect } from 'react' ;
22import { History , TrendingUp , X , Trash2 , Brain , Map } from 'lucide-react' ;
33import { ConversationEntry } from '../types' ;
44import { dbService } from '../services/db' ;
5+ import { getWidthClass } from '../src/utils/progressUtils' ;
56
67interface Props {
78 history : ConversationEntry [ ] ;
@@ -11,6 +12,25 @@ interface Props {
1112export const HistoryPanel : React . FC < Props > = ( { history, onClear } ) => {
1213 const [ isOpen , setIsOpen ] = useState ( false ) ;
1314
15+ // Handle click outside to close
16+ const handleBackdropClick = ( e : React . MouseEvent ) => {
17+ if ( e . target === e . currentTarget ) {
18+ setIsOpen ( false ) ;
19+ }
20+ } ;
21+
22+ // Handle ESC key
23+ useEffect ( ( ) => {
24+ const handleEsc = ( e : KeyboardEvent ) => {
25+ if ( e . key === 'Escape' && isOpen ) {
26+ setIsOpen ( false ) ;
27+ }
28+ } ;
29+
30+ window . addEventListener ( 'keydown' , handleEsc ) ;
31+ return ( ) => window . removeEventListener ( 'keydown' , handleEsc ) ;
32+ } , [ isOpen ] ) ;
33+
1434 // Memoize the heavy analysis logic so it doesn't run on every render
1535 const analysis = useMemo ( ( ) => {
1636 if ( history . length === 0 ) return null ;
@@ -28,48 +48,37 @@ export const HistoryPanel: React.FC<Props> = ({ history, onClear }) => {
2848 const avgEmotionalRegulation = displayData . reduce ( ( sum , e ) => sum + safeMetric ( e , 'emotional_regulation' ) , 0 ) / ( displayData . length || 1 ) ;
2949
3050 // DETERMINE MINDFULNESS PROFILE (clinically based)
31- let profileTitle = "Developing Awareness " ;
32- let description = "Bạn đang xây dựng nền tảng chánh niệm. " ;
33-
34- if ( avgPresentMomentAwareness > 0.7 && avgAttentionStability > 0.7 ) {
35- profileTitle = "Strong Practice" ; // High discipline
36- description = "Bạn đã phát triển khả năng định tâm vững chãi. " ;
37- } else if ( avgEmotionalRegulation > 0.7 ) {
38- profileTitle = "Emotional Balance" ; // High regulation
39- description = "Bạn điều tiết cảm xúc một cách khéo léo. " ;
40- } else if ( avgAttentionStability > 0.8 ) {
41- profileTitle = "Focused Attention" ; // High attention
42- description = "Bạn có khả năng tập trung ổn định tốt. " ;
51+ let profileTitle = "Beginning " ;
52+ let description = "Starting your mindfulness journey " ;
53+
54+ if ( avgPresentMomentAwareness >= 0.7 && avgEmotionalRegulation >= 0.7 && avgAttentionStability >= 0.7 ) {
55+ profileTitle = "Advanced" ;
56+ description = "Deep mindfulness practice established " ;
57+ } else if ( avgPresentMomentAwareness >= 0.5 && avgEmotionalRegulation >= 0.5 && avgAttentionStability >= 0.5 ) {
58+ profileTitle = "Intermediate" ;
59+ description = "Building consistent mindfulness habits " ;
60+ } else if ( avgPresentMomentAwareness >= 0.3 || avgEmotionalRegulation >= 0.3 || avgAttentionStability >= 0.3 ) {
61+ profileTitle = "Developing" ;
62+ description = "Early progress in mindfulness practice " ;
4363 }
4464
4565 return {
46- displayData,
66+ profileTitle,
67+ description,
4768 avgAttentionStability,
4869 avgPresentMomentAwareness,
4970 avgEmotionalRegulation,
50- profileTitle,
51- description
71+ displayData
5272 } ;
5373 } , [ history ] ) ;
5474
5575 if ( ! analysis ) return null ;
5676
57- const { displayData , avgAttentionStability, avgPresentMomentAwareness, avgEmotionalRegulation, profileTitle , description } = analysis ;
77+ const { profileTitle , description , avgAttentionStability, avgPresentMomentAwareness, avgEmotionalRegulation, displayData } = analysis ;
5878
59- const emotionColor = {
60- anxious : 'border-orange-400 bg-orange-50' ,
61- sad : 'border-blue-400 bg-blue-50' ,
62- joyful : 'border-yellow-400 bg-yellow-50' ,
63- calm : 'border-emerald-400 bg-emerald-50' ,
64- neutral : 'border-stone-300 bg-stone-50'
65- } ;
66-
67- const handleClear = async ( ) => {
68- if ( window . confirm ( 'Xóa toàn bộ lịch sử? (Không thể hoàn tác)' ) ) {
69- await dbService . clearAll ( ) ;
70- onClear ( ) ;
71- setIsOpen ( false ) ;
72- }
79+ const handleClear = ( ) => {
80+ onClear ( ) ;
81+ setIsOpen ( false ) ;
7382 } ;
7483
7584 return (
@@ -83,71 +92,83 @@ export const HistoryPanel: React.FC<Props> = ({ history, onClear }) => {
8392 </ button >
8493
8594 { isOpen && (
86- < div className = "absolute top-20 right-4 z-50 w-80 max-h-[70vh] flex flex-col bg-white/95 backdrop-blur-xl rounded-2xl shadow-2xl border border-stone-100 animate-[fadeIn_0.3s_ease-out]" >
87- { /* Header */ }
88- < div className = "bg-gradient-to-r from-stone-800 to-stone-900 text-amber-50 p-4 rounded-t-2xl flex items-center justify-between shadow-sm" >
89- < div className = "flex items-center gap-2" >
90- < Brain size = { 18 } className = "text-amber-400" />
91- < h3 className = "font-bold text-sm tracking-wide uppercase" > Mindfulness Profile</ h3 >
95+ < div
96+ className = "fixed inset-0 z-50 flex items-start justify-center pt-20 bg-black/40 backdrop-blur-sm p-4"
97+ onClick = { handleBackdropClick }
98+ >
99+ < div
100+ className = "w-full max-w-md bg-white/95 backdrop-blur-xl rounded-2xl shadow-2xl border border-stone-100 animate-[scaleIn_0.3s_ease-out] max-h-[70vh] overflow-hidden"
101+ onClick = { ( e ) => e . stopPropagation ( ) }
102+ >
103+ { /* Header */ }
104+ < div className = "bg-gradient-to-r from-stone-800 to-stone-900 text-amber-50 p-4 flex items-center justify-between shadow-sm" >
105+ < div className = "flex items-center gap-2" >
106+ < Brain size = { 18 } className = "text-amber-400" />
107+ < h3 className = "font-bold text-sm tracking-wide uppercase" > Mindfulness Profile</ h3 >
108+ </ div >
109+ < button
110+ onClick = { ( ) => setIsOpen ( false ) }
111+ className = "hover:bg-white/10 rounded-full p-1 transition-colors"
112+ aria-label = "Close history"
113+ >
114+ < X size = { 18 } />
115+ </ button >
92116 </ div >
93- < button onClick = { ( ) => setIsOpen ( false ) } className = "hover:bg-white/10 rounded-full p-1 transition-colors" >
94- < X size = { 18 } />
95- </ button >
96- </ div >
97-
98- { /* Mindfulness Profile (Clinical Metrics) */ }
99- < div className = "bg-stone-50 p-5 border-b border-stone-200" >
100- < div className = "text-center mb-3" >
101- < span className = "text-[10px] uppercase tracking-[0.2em] text-stone-400" > Practice Level</ span >
102- < h4 className = "text-xl font-serif font-bold text-stone-800 mt-1" > { profileTitle } </ h4 >
103- < p className = "text-xs text-stone-500 italic mt-1" > { description } </ p >
104- </ div >
105-
106- { /* Mindfulness Metrics Bar Chart */ }
107- < div className = "space-y-2 mt-4" >
108- < DnaBar label = "Present Moment" value = { avgPresentMomentAwareness } color = "bg-emerald-500" />
109- < DnaBar label = "Emotion Regulation" value = { avgEmotionalRegulation } color = "bg-purple-500" />
110- < DnaBar label = "Attention" value = { avgAttentionStability } color = "bg-blue-500" />
111- </ div >
112- </ div >
113117
114- { /* History list */ }
115- < div className = "overflow-y-auto p-4 space-y-3 flex-1 custom-scrollbar" >
116- < div className = "flex items-center gap-2 mb-2 text-stone-400" >
117- < TrendingUp size = { 12 } />
118- < span className = "text-[10px] uppercase font-bold tracking-wider" > Journey Log</ span >
118+ { /* Mindfulness Profile (Clinical Metrics) */ }
119+ < div className = "bg-stone-50 p-5 border-b border-stone-200" >
120+ < div className = "text-center mb-3" >
121+ < span className = "text-[10px] uppercase tracking-[0.2em] text-stone-400" > Practice Level</ span >
122+ < h4 className = "text-xl font-serif font-bold text-stone-800 mt-1" > { profileTitle } </ h4 >
123+ < p className = "text-xs text-stone-500 italic mt-1" > { description } </ p >
124+ </ div >
125+
126+ { /* Mindfulness Metrics Bar Chart */ }
127+ < div className = "space-y-2 mt-4" >
128+ < DnaBar label = "Present Moment" value = { avgPresentMomentAwareness } color = "bg-emerald-500" />
129+ < DnaBar label = "Emotion Regulation" value = { avgEmotionalRegulation } color = "bg-purple-500" />
130+ < DnaBar label = "Attention" value = { avgAttentionStability } color = "bg-blue-500" />
131+ </ div >
119132 </ div >
120-
121- { [ ...displayData ] . reverse ( ) . map ( ( entry ) => {
122- const date = new Date ( entry . timestamp ) ;
123- const isToday = date . toDateString ( ) === new Date ( ) . toDateString ( ) ;
124- const timeStr = isToday
125- ? date . toLocaleTimeString ( 'vi-VN' , { hour : '2-digit' , minute : '2-digit' } )
126- : date . toLocaleDateString ( 'vi-VN' , { month : 'numeric' , day : 'numeric' } ) ;
127-
128- return (
129- < div key = { entry . id } className = { `border-l-[3px] ${ emotionColor [ entry . emotion ] || emotionColor . neutral } rounded-r-lg p-3 transition-all hover:bg-stone-50` } >
130- < div className = "flex items-center justify-between mb-1" >
131- < span className = "text-[10px] font-bold text-stone-600 uppercase tracking-widest" > { entry . emotion } </ span >
132- < span className = "text-[10px] text-stone-400 font-mono" > { timeStr } </ span >
133- </ div >
134- < div className = "flex gap-3 text-[10px] font-medium opacity-80" >
135- < span className = "text-blue-600" > Attn: { Math . round ( ( entry . mindfulness_metrics ?. attention_stability || 0 ) * 100 ) } </ span >
136- < span className = "text-purple-600" > Reg: { Math . round ( ( entry . mindfulness_metrics ?. emotional_regulation || 0 ) * 100 ) } </ span >
133+
134+ { /* History list */ }
135+ < div className = "overflow-y-auto p-4 space-y-3 flex-1 custom-scrollbar" >
136+ < div className = "flex items-center gap-2 mb-2 text-stone-400" >
137+ < TrendingUp size = { 12 } />
138+ < span className = "text-[10px] uppercase font-bold tracking-wider" > Journey Log</ span >
139+ </ div >
140+ { displayData . map ( ( entry , idx ) => {
141+ const timeStr = new Date ( entry . timestamp ) . toLocaleTimeString ( 'en-US' , {
142+ hour : '2-digit' ,
143+ minute : '2-digit'
144+ } ) ;
145+ return (
146+ < div
147+ key = { entry . id }
148+ className = "bg-white border border-stone-200 rounded-lg p-3 shadow-sm hover:shadow-md transition-shadow"
149+ >
150+ < div className = "flex items-center justify-between mb-1" >
151+ < span className = "text-[10px] font-bold text-stone-600 uppercase tracking-widest" > { entry . emotion } </ span >
152+ < span className = "text-[10px] text-stone-400 font-mono" > { timeStr } </ span >
153+ </ div >
154+ < div className = "flex gap-3 text-[10px] font-medium opacity-80" >
155+ < span className = "text-blue-600" > Attn: { Math . round ( ( entry . mindfulness_metrics ?. attention_stability || 0 ) * 100 ) } </ span >
156+ < span className = "text-purple-600" > Reg: { Math . round ( ( entry . mindfulness_metrics ?. emotional_regulation || 0 ) * 100 ) } </ span >
157+ </ div >
137158 </ div >
138- </ div >
139- ) ;
140- } ) }
141- </ div >
159+ ) ;
160+ } ) }
161+ </ div >
142162
143- { /* Clear button */ }
144- < div className = "border-t border-stone-100 p-3 bg-stone-50/50 rounded-b-2xl" >
145- < button
146- onClick = { handleClear }
147- className = "w-full py-2 text-xs font-medium text-stone-500 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors flex items-center justify-center gap-2"
148- >
149- < Trash2 size = { 14 } /> Xóa lịch sử
150- </ button >
163+ { /* Clear button */ }
164+ < div className = "border-t border-stone-100 p-3 bg-stone-50/50 rounded-b-2xl" >
165+ < button
166+ onClick = { handleClear }
167+ className = "w-full py-2 text-xs font-medium text-stone-500 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors flex items-center justify-center gap-2"
168+ >
169+ < Trash2 size = { 14 } /> Clear History
170+ </ button >
171+ </ div >
151172 </ div >
152173 </ div >
153174 ) }
@@ -160,9 +181,8 @@ const DnaBar = ({ label, value, color }: { label: string, value: number, color:
160181 < span className = "text-[10px] font-bold text-stone-400 w-16 text-right" > { label } </ span >
161182 < div className = "flex-1 h-1.5 bg-stone-200 rounded-full overflow-hidden" >
162183 < div
163- className = { `h-full ${ color } rounded-full` }
164- style = { { width : `${ value * 100 } %` } }
184+ className = { `h-full ${ color } rounded-full dna-bar-fill ${ getWidthClass ( value * 100 ) } ` }
165185 />
166186 </ div >
167187 </ div >
168- ) ;
188+ ) ;
0 commit comments