@@ -15,6 +15,25 @@ interface HistoryEntry {
1515 spent : number ;
1616}
1717
18+ function FlameIcon ( ) {
19+ return (
20+ < svg viewBox = "0 0 24 24" fill = "none" xmlns = "http://www.w3.org/2000/svg" >
21+ < path d = "M12 2C12 2 6 8.5 6 14C6 17.31 8.69 20 12 20C15.31 20 18 17.31 18 14C18 8.5 12 2 12 2Z" fill = "url(#flame-grad)" />
22+ < path d = "M12 20C10.34 20 9 18.66 9 17C9 14.5 12 11 12 11C12 11 15 14.5 15 17C15 18.66 13.66 20 12 20Z" fill = "url(#flame-inner)" />
23+ < defs >
24+ < linearGradient id = "flame-grad" x1 = "12" y1 = "2" x2 = "12" y2 = "20" gradientUnits = "userSpaceOnUse" >
25+ < stop stopColor = "#fbbf24" />
26+ < stop offset = "1" stopColor = "#f97316" />
27+ </ linearGradient >
28+ < linearGradient id = "flame-inner" x1 = "12" y1 = "11" x2 = "12" y2 = "20" gradientUnits = "userSpaceOnUse" >
29+ < stop stopColor = "#fde68a" />
30+ < stop offset = "1" stopColor = "#fbbf24" />
31+ </ linearGradient >
32+ </ defs >
33+ </ svg >
34+ ) ;
35+ }
36+
1837export function SavingsStreak ( { dailyBudget, todaySpent } : SavingsStreakProps ) {
1938 const { locale, fmt } = useLocale ( ) ;
2039 const [ history , setHistory ] = useState < HistoryEntry [ ] > ( [ ] ) ;
@@ -44,11 +63,11 @@ export function SavingsStreak({ dailyBudget, todaySpent }: SavingsStreakProps) {
4463 } ) . catch ( ( ) => { } ) ;
4564 } , [ dailyBudget , todaySpent ] ) ;
4665
47- // Last 12 days from history
66+ // Last 7 days from history
4867 const days : { day : number ; month : number ; status : "fire" | "fail" | "today" | "nodata" ; budget : number ; spent : number } [ ] = [ ] ;
4968 let currentStreak = 0 ;
5069
51- for ( let d = 11 ; d >= 0 ; d -- ) {
70+ for ( let d = 6 ; d >= 0 ; d -- ) {
5271 const date = new Date ( now ) ;
5372 date . setDate ( today - d ) ;
5473 const dateStr = `${ date . getFullYear ( ) } -${ String ( date . getMonth ( ) + 1 ) . padStart ( 2 , "0" ) } -${ String ( date . getDate ( ) ) . padStart ( 2 , "0" ) } ` ;
@@ -57,13 +76,11 @@ export function SavingsStreak({ dailyBudget, todaySpent }: SavingsStreakProps) {
5776 const monthNum = date . getMonth ( ) + 1 ;
5877
5978 if ( d === 0 ) {
60- // Today — use current props for spending
6179 days . push ( { day : dayNum , month : monthNum , status : "today" , budget : dailyBudget , spent : todaySpent } ) ;
6280 continue ;
6381 }
6482
6583 if ( ! entry ) {
66- // No data recorded — mark as fail (unknown)
6784 currentStreak = 0 ;
6885 days . push ( { day : dayNum , month : monthNum , status : "nodata" , budget : 0 , spent : 0 } ) ;
6986 continue ;
@@ -86,29 +103,36 @@ export function SavingsStreak({ dailyBudget, todaySpent }: SavingsStreakProps) {
86103
87104 return (
88105 < Card className = "metric-card savings-streak-card" >
89- < p className = "metric-card-label" > { locale === "fi" ? "Säästöputki" : "Savings streak" } </ p >
90- < div className = "savings-streak-dots" >
91- { days . map ( ( d , i ) => (
92- < div
93- key = { i }
94- className = { `savings-streak-dot ${ d . status === "fire" ? "is-fire" : d . status === "fail" ? "is-fail" : d . status === "today" ? ( dailyBudget > 0 && d . spent <= dailyBudget ? "is-fire" : "is-today" ) : "is-neutral" } ` }
95- >
96- { ( d . status === "fire" || ( d . status === "today" && dailyBudget > 0 && d . spent <= dailyBudget ) ) && < span className = "savings-streak-fire-icon" /> }
97- { d . status === "fail" && < span className = "savings-streak-x-icon" /> }
98- { d . status === "today" && dailyBudget > 0 && d . spent > dailyBudget && < span className = "savings-streak-x-icon" /> }
99- { d . status === "nodata" && < span className = "savings-streak-neutral-dot" /> }
100- < span className = "savings-streak-tooltip" >
101- < span > { d . day } .{ d . month } .</ span >
102- < span > { d . status === "nodata" ? ( locale === "fi" ? "ei dataa" : "no data" ) : `${ fmt ( d . status === "today" ? todaySpent : d . spent ) } /${ fmt ( d . budget ) } €` } </ span >
103- </ span >
106+ < div className = "metric-card-row" >
107+ < div className = "metric-card-icon savings-streak-flame" >
108+ < FlameIcon />
109+ </ div >
110+ < div >
111+ < p className = "metric-card-label" > { locale === "fi" ? "Säästöputki" : "Savings streak" } </ p >
112+ < div className = "savings-streak-dots" >
113+ { days . map ( ( d , i ) => (
114+ < div
115+ key = { i }
116+ className = { `savings-streak-dot ${ d . status === "fire" ? "is-fire" : d . status === "fail" ? "is-fail" : d . status === "today" ? ( dailyBudget > 0 && d . spent <= dailyBudget ? "is-fire" : "is-today" ) : "is-neutral" } ` }
117+ >
118+ { ( d . status === "fire" || ( d . status === "today" && dailyBudget > 0 && d . spent <= dailyBudget ) ) && < span className = "savings-streak-fire-icon" /> }
119+ { d . status === "fail" && < span className = "savings-streak-x-icon" /> }
120+ { d . status === "today" && dailyBudget > 0 && d . spent > dailyBudget && < span className = "savings-streak-x-icon" /> }
121+ { d . status === "nodata" && < span className = "savings-streak-neutral-dot" /> }
122+ < span className = "savings-streak-tooltip" >
123+ < span > { d . day } .{ d . month } .</ span >
124+ < span > { d . status === "nodata" ? ( locale === "fi" ? "ei dataa" : "no data" ) : `${ fmt ( d . status === "today" ? todaySpent : d . spent ) } /${ fmt ( d . budget ) } €` } </ span >
125+ </ span >
126+ </ div >
127+ ) ) }
104128 </ div >
105- ) ) }
129+ < p className = "metric-card-note" >
130+ { currentStreak > 0
131+ ? ( locale === "fi" ? `${ currentStreak } päivää putkeen alle budjetin` : `${ currentStreak } days in a row under budget` )
132+ : ( locale === "fi" ? "Ei putkea vielä" : "No streak yet" ) }
133+ </ p >
134+ </ div >
106135 </ div >
107- < p className = "metric-card-note" >
108- { currentStreak > 0
109- ? ( locale === "fi" ? `Olet onnistunut selviämään alle päiväbudjetin ${ currentStreak } päivää putkeen.` : `You've stayed under budget ${ currentStreak } days in a row.` )
110- : ( locale === "fi" ? "Ei putkea vielä. Pysy budjetissa tänään!" : "No streak yet. Stay under budget today!" ) }
111- </ p >
112136 </ Card >
113137 ) ;
114138}
0 commit comments