Skip to content

Commit f05d255

Browse files
committed
update calender component
1 parent 26e6f6e commit f05d255

10 files changed

Lines changed: 195 additions & 71 deletions

File tree

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@ All expenses are stored in the device's **LocalStorage** and reflected immediate
5353
* Tap any date to open a **popup modal** displaying all expenses for that day.
5454
* Navigate across **months and years** to view past or future expenses.
5555
* Toggle the Show HeatMap switch to highlight each day based on spending intensity.
56-
- Days are color-coded (e.g. No expense, < ₹300, ₹300–₹1000, > ₹1000) for quick insights.
57-
- A legend below the calendar explains each color category.
58-
- A summary table displays color, days count, and total amount for each category.
56+
* Days are color-coded based on spending thresholds (e.g. No expense, < threshold, between thresholds, > threshold) for quick insights.
57+
* Users can now **customize the threshold amounts** for each heatmap color:
58+
* **Red (Rose)** – default > ₹1000
59+
* **Yellow (Amber)** – default ₹500 - 1000
60+
* **Green (Emerald)** – default < ₹500
61+
* A **summary table** displays each color, the number of days, the total expense, and an **Edit button** for updating the thresholds.
62+
* Editing allows users to set a new amount for the corresponding color, immediately updating the heatmap visualization.
5963

6064
---
6165

src/app/features/calendar/calendar.component.html

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<!-- ░░ CALENDAR ░░ -->
4343
<div>
4444
<h2
45-
class="border-b border-[var(--color-white)] p-2 bg-[var(--color-accent)] text-[var(--color-white)] text-lg font-semibold text-center">
45+
class="rounded-t-lg border-b border-[var(--color-white)] p-2 bg-[var(--color-accent)] text-[var(--color-white)] text-lg font-semibold text-center">
4646
{{ calendarTitle }}
4747
</h2>
4848

@@ -62,53 +62,46 @@
6262
</div>
6363
</div>
6464

65-
<!-- ░░ HEATMAP LEGEND ░░ -->
66-
<div class="mt-6" *ngIf="isShowHeatmap">
67-
<div class="flex justify-around md:justify-center md:gap-4 text-[0.8rem]">
68-
<div class="flex items-center gap-1">
69-
<span class="w-4 h-4 rounded bg-[var(--color-gray)] inline-block"></span>
70-
<span class="text-[var(--color-text)]">No expense</span>
71-
</div>
72-
<div class="flex items-center gap-1">
73-
<span class="w-4 h-4 rounded bg-[var(--color-emerald)] inline-block"></span>
74-
<span class="text-[var(--color-text)]">&lt; 300</span>
75-
</div>
76-
<div class="flex items-center gap-1">
77-
<span class="w-4 h-4 rounded bg-[var(--color-amber)] inline-block"></span>
78-
<span class="text-[var(--color-text)]">300 – 1000</span>
79-
</div>
80-
<div class="flex items-center gap-1">
81-
<span class="w-4 h-4 rounded bg-[var(--color-rose)] inline-block"></span>
82-
<span class="text-[var(--color-text)]">&gt; 1000</span>
83-
</div>
84-
</div>
85-
</div>
86-
8765
<!-- ░░ HEATMAP SUMMARY ░░ -->
8866
<div class="my-6 text-center" *ngIf="heatmapSummary.length > 0">
8967
<div
9068
class="max-w-[300px] md:max-w-md mx-auto w-full border border-[var(--input-border)] rounded-lg overflow-hidden text-sm">
9169

9270
<!-- Table Header -->
93-
<div class="grid grid-cols-3 bg-[var(--color-gray)] text-[var(--color-text)] font-semibold py-2">
94-
<span>Color</span>
95-
<span>Days</span>
96-
<span>Amount</span>
71+
<div class="grid grid-cols-[40%_15%_30%_15%] bg-[var(--color-gray)] text-[var(--color-text)] font-semibold py-2 text-left">
72+
<span class="pl-3">Color</span>
73+
<span class="text-center">Days</span>
74+
<span class="text-center">Amount</span>
75+
<span class="text-center">Edit</span>
9776
</div>
9877

9978
<!-- Table Rows -->
10079
<div *ngFor="let item of heatmapSummary"
101-
class="grid grid-cols-3 items-center py-2 border-t border-[var(--input-border)] text-[var(--color-text)] text-[0.8rem] hover:bg-[var(--list-hover)]">
102-
<span class="flex justify-center">
80+
class="grid grid-cols-[40%_15%_30%_15%] items-center py-2 border-t border-[var(--input-border)] text-[var(--color-text)] text-[0.8rem]">
81+
82+
<!-- Color + Label -->
83+
<div class="flex items-center justify-start gap-2 pl-2">
10384
<span class="w-4 h-4 rounded inline-block" [ngClass]="item.color"></span>
104-
</span>
105-
<span class="font-medium">{{ item.days }}</span>
106-
<span class="font-medium">{{ currency }} {{ item.amount}}</span>
85+
<span>{{ item.text }}</span>
86+
</div>
87+
88+
<!-- Days -->
89+
<div class="font-medium text-center">{{ item.days }}</div>
90+
91+
<!-- Amount -->
92+
<div class="font-medium text-center">{{ currency }} {{ item.amount }}</div>
93+
<!-- Edit button (perfectly centered) -->
94+
<div class="flex items-center justify-center">
95+
<button *ngIf="!(item.text === 'No expenses')" (click)="openEditHeapMapModel(item.color)"
96+
class="w-7 h-7 flex items-center justify-center rounded-full hover:bg-[var(--list-hover)] transition-all duration-200 ease-in-out">
97+
<img src="assets/img/icon/icons8-edit-50.png" alt="edit"
98+
class="w-[14px] h-[14px] pointer-events-none" style="filter: var(--icon-color2);" />
99+
</button>
100+
</div>
107101
</div>
108-
102+
109103
<!-- Total Row -->
110-
<div class="grid grid-cols-3 items-center py-2 border-t border-[var(--input-border)]
111-
text-[var(--color-text)] font-semibold">
104+
<div class="grid grid-cols-[40%_15%_30%_15%] items-center py-2 border-t border-[var(--input-border)] text-[var(--color-text)] font-semibold">
112105
<span class="col-span-2 text-center">Total</span>
113106
<span class="text-center text-green-500">{{ currency }} {{ totalExpenses }}</span>
114107
</div>
@@ -147,4 +140,21 @@ <h2 class="text-lg font-bold text-[var(--color-text)]">{{ modalTitle }}</h2>
147140
</ul>
148141
</div>
149142
</div>
150-
</section>
143+
</section>
144+
145+
<app-form-model *ngIf="showEditHeatMapModel" [label]="'Edit HeatMap'" (close)="closeEditHeatMapModel()">
146+
<form [formGroup]="heatMapForm" (ngSubmit)="updateHeatMap()">
147+
<label class="block mb-1 font-medium">From Date</label>
148+
<input formControlName="amount" type="number" autocomplete="off" min="0" max="100000000" placeholder="Enter Amount"
149+
class="w-full p-2 rounded border text-[var(--input-text)] bg-[var(--input-bg)] border-[var(--input-border)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-color)] transition-all duration-200" />
150+
<div class="error-message text-[var(--color-error)]"
151+
*ngIf="heatMapForm.get('amount')?.touched && heatMapForm.get('amount')?.errors?.['required']">
152+
Amount is required
153+
</div>
154+
<div class="flex justify-end mt-2">
155+
<button type="button" (click)="closeEditHeatMapModel()"
156+
class="mr-2 px-4 py-2 bg-[var(--color-gray-500)] text-white rounded">Cancel</button>
157+
<button type="submit" class="px-4 py-2 bg-[var(--color-accent)] text-white rounded">Update</button>
158+
</div>
159+
</form>
160+
</app-form-model>

src/app/features/calendar/calendar.component.ts

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { Component, OnInit } from '@angular/core';
22
import { ExpenseService, Expense } from '../../service/localStorage/expense.service';
33
import { CommonModule } from '@angular/common';
44
import { UserService } from '../../service/localStorage/user.service';
5-
import { FormsModule } from '@angular/forms';
65
import { HeatmapSummary } from '../../models/heatMap-summary.service';
6+
import { FormModelComponent } from '../../component/form-model/form-model.component';
7+
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
78

89
/**
910
* Component that renders a monthly calendar view with expense tracking.
@@ -16,9 +17,9 @@ import { HeatmapSummary } from '../../models/heatMap-summary.service';
1617
*/
1718
@Component({
1819
selector: 'app-calendar',
19-
imports: [CommonModule, FormsModule],
20+
imports: [CommonModule, FormModelComponent, FormsModule, ReactiveFormsModule],
2021
templateUrl: './calendar.component.html',
21-
styleUrl: './calendar.component.css'
22+
styleUrls: ['./calendar.component.css']
2223
})
2324
export class CalendarComponent implements OnInit {
2425

@@ -65,16 +66,32 @@ export class CalendarComponent implements OnInit {
6566
*/
6667
heatmapSummary: HeatmapSummary[] = [];
6768

69+
/** Controls the visibility of the Edit Heatmap modal */
70+
showEditHeatMapModel = false;
71+
72+
/** Form group for handling Heatmap edit form inputs and validations */
73+
heatMapForm!: FormGroup;
74+
75+
/** Tracks whether the Rose color modal is currently open */
76+
isRoseModelOpen: boolean = false;
77+
78+
/** Tracks whether the Emerald color modal is currently open */
79+
isEmeraldModelOpen: boolean = false;
80+
81+
/** Tracks whether the Amber color modal is currently open */
82+
isAmberModelOpen: boolean = false;
6883

6984
/**
7085
* Creates an instance of CalendarComponent.
7186
*
7287
* @param expenseService Service to retrieve expenses from local storage.
7388
* @param userService Service to retrieve user settings such as currency.
89+
* @param fb Angular `FormBuilder` to build the reactive form.
7490
*/
7591
constructor(
7692
private expenseService: ExpenseService,
77-
public userService: UserService
93+
public userService: UserService,
94+
private fb: FormBuilder,
7895
) {
7996
this.currency = this.userService.getValue<string>('currency');
8097
this.isShowHeatmap = this.userService.getValue<boolean>('is_show_heatmap') ?? false;
@@ -84,6 +101,9 @@ export class CalendarComponent implements OnInit {
84101
/** Angular lifecycle hook that initializes the calendar view */
85102
ngOnInit(): void {
86103
this.renderCalendar(this.currentYear, this.currentMonth);
104+
this.heatMapForm = this.fb.group({
105+
amount: [0, [Validators.required]]
106+
});
87107
}
88108

89109
/**
@@ -208,19 +228,21 @@ export class CalendarComponent implements OnInit {
208228
*/
209229
private getHeatClass(amount: number): string {
210230
if (this.isShowHeatmap === false) return 'bg-[var(--color-surface)]';
231+
const rose_amount = this.userService.getValue<number>('rose_amount') ?? 1000;
232+
const emerald_amount = this.userService.getValue<number>('emerald_amount') ?? 300;
211233
if (amount === 0) {
212-
this.addOrUpdateHeatMapSummary('bg-[var(--color-gray)]', amount)
234+
this.addOrUpdateHeatMapSummary('bg-[var(--color-gray)]', amount, 'No expenses')
213235
return 'bg-[var(--color-gray)]';
214236
}
215-
if (amount < 300) {
216-
this.addOrUpdateHeatMapSummary('bg-[var(--color-emerald)]', amount)
237+
if (amount < emerald_amount) {
238+
this.addOrUpdateHeatMapSummary('bg-[var(--color-emerald)]', amount, `< ${emerald_amount}`)
217239
return 'bg-[var(--color-emerald)]';
218240
}
219-
if (amount < 1000) {
220-
this.addOrUpdateHeatMapSummary('bg-[var(--color-amber)]', amount)
241+
if (amount < rose_amount) {
242+
this.addOrUpdateHeatMapSummary('bg-[var(--color-amber)]', amount, `${emerald_amount} - ${rose_amount}`)
221243
return 'bg-[var(--color-amber)]';
222244
}
223-
this.addOrUpdateHeatMapSummary('bg-[var(--color-rose)]', amount)
245+
this.addOrUpdateHeatMapSummary('bg-[var(--color-rose)]', amount, `> ${rose_amount}`)
224246
return 'bg-[var(--color-rose)]';
225247
}
226248

@@ -252,7 +274,7 @@ export class CalendarComponent implements OnInit {
252274
* it increments the `days` count by 1 and adds the `amount` to the existing total.
253275
* Otherwise, it creates a new entry with `days` initialized to 1 and `amount` as provided.
254276
*/
255-
addOrUpdateHeatMapSummary(color: string, amount: number) {
277+
addOrUpdateHeatMapSummary(color: string, amount: number, message: string) {
256278
const existing = this.heatmapSummary.find(item => item.color === color);
257279
if (existing) {
258280
existing.days += 1;
@@ -261,8 +283,68 @@ export class CalendarComponent implements OnInit {
261283
this.heatmapSummary.push({
262284
color: color,
263285
days: 1,
264-
amount: amount
286+
amount: amount,
287+
text: message
288+
});
289+
}
290+
this.heatmapSummary.sort((a, b) => b.amount - a.amount);
291+
}
292+
293+
/**
294+
* Closes the Edit Heatmap modal and resets all color-specific modal states.
295+
*/
296+
closeEditHeatMapModel(): void {
297+
this.showEditHeatMapModel = false;
298+
this.isEmeraldModelOpen = false;
299+
this.isRoseModelOpen = false;
300+
this.isAmberModelOpen = false;
301+
}
302+
303+
/**
304+
* Opens the Edit Heatmap modal for a specific color.
305+
* Resets the form with the corresponding saved amount value.
306+
*
307+
* @param color - The background color class of the heatmap block ('bg-[var(--color-rose)]', 'bg-[var(--color-emerald)]', 'bg-[var(--color-amber)]')
308+
*/
309+
openEditHeapMapModel(color: string): void {
310+
if (color === 'bg-[var(--color-rose)]') {
311+
this.heatMapForm.reset({
312+
amount: this.userService.getValue<number>('rose_amount') ?? 1000,
313+
});
314+
this.isRoseModelOpen = true;
315+
}
316+
if (color === 'bg-[var(--color-emerald)]') {
317+
this.heatMapForm.reset({
318+
amount: this.userService.getValue<number>('emerald_amount') ?? 300,
265319
});
320+
this.isEmeraldModelOpen = true;
266321
}
322+
if (color === 'bg-[var(--color-amber)]') {
323+
this.heatMapForm.reset({
324+
amount: this.userService.getValue<number>('emerald_amount') ?? 300,
325+
});
326+
this.isAmberModelOpen = true;
327+
}
328+
this.showEditHeatMapModel = !this.showEditHeatMapModel;
329+
}
330+
331+
/**
332+
* Validates and updates the Heatmap amount for the currently open color modal.
333+
* Persists the new amount to the user service and refreshes the calendar view.
334+
*/
335+
updateHeatMap(): void {
336+
if (this.heatMapForm.invalid) {
337+
this.heatMapForm.markAllAsTouched();
338+
return;
339+
}
340+
const { amount } = this.heatMapForm.value;
341+
if (this.isEmeraldModelOpen || this.isAmberModelOpen) {
342+
this.userService.update('emerald_amount', amount);
343+
}
344+
if (this.isRoseModelOpen) {
345+
this.userService.update('rose_amount', amount);
346+
}
347+
this.renderCalendar(this.currentYear, this.currentMonth);
348+
this.closeEditHeatMapModel();
267349
}
268350
}

src/app/models/heatMap-summary.service.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* Represents a single heatmap summary entry.
3-
* This interface is used to describe the color intensity, total days,
4-
* and total expense amount for a specific heat level in the calendar heatmap.
3+
* Describes the color intensity, total days, total expense amount,
4+
* and label text for a specific heat level in the calendar heatmap.
55
*/
66
export interface HeatmapSummary {
77
/**
@@ -21,4 +21,10 @@ export interface HeatmapSummary {
2121
* Helps calculate and display the spending range for the legend.
2222
*/
2323
amount: number;
24+
25+
/**
26+
* The label or descriptive text associated with this heat level.
27+
* Can be used in UI legends or tooltips.
28+
*/
29+
text: string;
2430
}

src/app/service/localStorage/storage.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ export class StorageService {
6969
is_app_updated: true,
7070
is_show_heatmap: false,
7171
has_music_url_access: false,
72-
has_ai_access: false
72+
has_ai_access: false,
73+
rose_amount: 1000,
74+
emerald_amount: 300,
7375
};
7476

7577
/** Schema for budget */

src/app/service/localStorage/user.service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ export interface User {
3434

3535
/** Flag to determine if the user can access ai ssection for analysis data*/
3636
has_ai_access: boolean;
37+
38+
/** Stores the amount associated with the Rose heatmap color */
39+
rose_amount: number;
40+
41+
/** Stores the amount associated with the Emerald heatmap color */
42+
emerald_amount: number;
43+
3744
}
3845

3946
/**

0 commit comments

Comments
 (0)