calendar-notes-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.vscode/extensions.json +4 -0
  2. package/.vscode/launch.json +20 -0
  3. package/.vscode/mcp.json +9 -0
  4. package/.vscode/tasks.json +42 -0
  5. package/backend/policies/RLS.sql +1 -0
  6. package/backend/policies/delete.sql +6 -0
  7. package/backend/policies/insert.sql +6 -0
  8. package/backend/policies/select.sql +4 -0
  9. package/backend/policies/update.sql +8 -0
  10. package/backend/table.sql +9 -0
  11. package/calendar-notes-plugin/.editorconfig +17 -0
  12. package/calendar-notes-plugin/.hintrc +5 -0
  13. package/calendar-notes-plugin/.vscode/extensions.json +4 -0
  14. package/calendar-notes-plugin/.vscode/launch.json +20 -0
  15. package/calendar-notes-plugin/.vscode/mcp.json +9 -0
  16. package/calendar-notes-plugin/.vscode/tasks.json +42 -0
  17. package/calendar-notes-plugin/README.md +226 -0
  18. package/calendar-notes-plugin/angular.json +74 -0
  19. package/calendar-notes-plugin/package-lock.json +8917 -0
  20. package/calendar-notes-plugin/package.json +52 -0
  21. package/calendar-notes-plugin/public/favicon.ico +0 -0
  22. package/calendar-notes-plugin/public/widget.js +75 -0
  23. package/calendar-notes-plugin/src/app/app.config.ts +12 -0
  24. package/calendar-notes-plugin/src/app/app.css +0 -0
  25. package/calendar-notes-plugin/src/app/app.html +1 -0
  26. package/calendar-notes-plugin/src/app/app.routes.server.ts +8 -0
  27. package/calendar-notes-plugin/src/app/app.routes.ts +3 -0
  28. package/calendar-notes-plugin/src/app/app.spec.ts +23 -0
  29. package/calendar-notes-plugin/src/app/app.ts +17 -0
  30. package/calendar-notes-plugin/src/app/components/calender/calendar.css +73 -0
  31. package/calendar-notes-plugin/src/app/components/calender/calendar.html +26 -0
  32. package/calendar-notes-plugin/src/app/components/calender/calendar.spec.ts +23 -0
  33. package/calendar-notes-plugin/src/app/components/calender/calendar.ts +155 -0
  34. package/calendar-notes-plugin/src/app/components/floating-button/floating-button.css +17 -0
  35. package/calendar-notes-plugin/src/app/components/floating-button/floating-button.html +3 -0
  36. package/calendar-notes-plugin/src/app/components/floating-button/floating-button.spec.ts +23 -0
  37. package/calendar-notes-plugin/src/app/components/floating-button/floating-button.ts +25 -0
  38. package/calendar-notes-plugin/src/app/components/notes/notes.css +251 -0
  39. package/calendar-notes-plugin/src/app/components/notes/notes.html +107 -0
  40. package/calendar-notes-plugin/src/app/components/notes/notes.spec.ts +23 -0
  41. package/calendar-notes-plugin/src/app/components/notes/notes.ts +328 -0
  42. package/calendar-notes-plugin/src/app/components/panel/panel.css +28 -0
  43. package/calendar-notes-plugin/src/app/components/panel/panel.html +16 -0
  44. package/calendar-notes-plugin/src/app/components/panel/panel.spec.ts +23 -0
  45. package/calendar-notes-plugin/src/app/components/panel/panel.ts +46 -0
  46. package/calendar-notes-plugin/src/app/components/widget/widget.css +18 -0
  47. package/calendar-notes-plugin/src/app/components/widget/widget.html +17 -0
  48. package/calendar-notes-plugin/src/app/components/widget/widget.spec.ts +23 -0
  49. package/calendar-notes-plugin/src/app/components/widget/widget.ts +23 -0
  50. package/calendar-notes-plugin/src/app/services/api.spec.ts +16 -0
  51. package/calendar-notes-plugin/src/app/services/api.ts +39 -0
  52. package/calendar-notes-plugin/src/app/services/notification.spec.ts +16 -0
  53. package/calendar-notes-plugin/src/app/services/notification.ts +116 -0
  54. package/calendar-notes-plugin/src/backend/database/policies/delete.sql +6 -0
  55. package/calendar-notes-plugin/src/backend/database/policies/insert.sql +6 -0
  56. package/calendar-notes-plugin/src/backend/database/policies/select.sql +6 -0
  57. package/calendar-notes-plugin/src/backend/database/policies/update.sql +6 -0
  58. package/calendar-notes-plugin/src/backend/database/table.sql +13 -0
  59. package/calendar-notes-plugin/src/index.html +13 -0
  60. package/calendar-notes-plugin/src/main.ts +34 -0
  61. package/calendar-notes-plugin/src/styles.css +1 -0
  62. package/calendar-notes-plugin/tsconfig.app.json +17 -0
  63. package/calendar-notes-plugin/tsconfig.json +33 -0
  64. package/calendar-notes-plugin/tsconfig.spec.json +15 -0
  65. package/calendar-notes-plugin-1.0.0.tgz +0 -0
  66. package/package.json +29 -0
@@ -0,0 +1,251 @@
1
+ :host {
2
+ display: flex;
3
+ flex: 1;
4
+ min-height: 0;
5
+ }
6
+
7
+ .notes {
8
+ display: flex;
9
+ flex-direction: column;
10
+ flex: 1;
11
+ padding: 5px;
12
+ gap: 10px;
13
+ overflow: hidden;
14
+ margin-top: 0;
15
+ min-height: 0;
16
+ }
17
+
18
+ /* Inputs */
19
+ .notes input{
20
+ height: 32px;
21
+ width: 100%;
22
+ box-sizing: border-box;
23
+ padding: 8px;
24
+ border-radius: 8px;
25
+ border: 1px solid #ddd;
26
+ }
27
+ .notes textarea {
28
+ width: 100%;
29
+ box-sizing: border-box;
30
+ padding: 8px;
31
+ border-radius: 8px;
32
+ border: 1px solid #ddd;
33
+ height: 60px;
34
+ }
35
+
36
+ .notes textarea {
37
+ resize: none;
38
+ min-height: 70px;
39
+ }
40
+
41
+
42
+ /* Form action wrapper */
43
+ .form-actions {
44
+ display: flex;
45
+ justify-content: flex-end; /* pushes button to right */
46
+ gap: 10px;
47
+ margin-top: 10px;
48
+ }
49
+
50
+ /* Save button */
51
+ .save-btn {
52
+ padding: 6px 16px;
53
+ border-radius: 8px;
54
+ border: none;
55
+ background: #4f46e5;
56
+ color: white;
57
+ cursor: pointer;
58
+ }
59
+
60
+ .cancel-btn {
61
+ padding: 6px 16px;
62
+ border-radius: 8px;
63
+ border: 1px solid #ddd;
64
+ background: white;
65
+ cursor: pointer;
66
+ }
67
+
68
+ .cancel-btn:hover {
69
+ background: #f3f4f6;
70
+ }
71
+
72
+ .save-btn:hover {
73
+ background: #4338ca;
74
+ }
75
+ /* REAL SCROLL FIX */
76
+ .existing-notes {
77
+ border-top: 1px solid #eee;
78
+ padding-top: 2px;
79
+ }
80
+
81
+ /* Individual note */
82
+ .existing-notes div {
83
+ background: #f3f4f6;
84
+ padding: 4px;
85
+ border-radius: 8px;
86
+ margin-bottom: 4px;
87
+ }
88
+
89
+ .existing-notes p {
90
+ margin: 4px 0 0 0;
91
+ font-size: 12px;
92
+ }
93
+
94
+ .notes h4,
95
+ .notes h5 {
96
+ margin: 0; /* removes browser default gap */
97
+ }
98
+
99
+ .add-bar {
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: space-between;
103
+ padding: 12px 14px;
104
+ border-radius: 14px;
105
+ background: #ffffff;
106
+ border: 1px solid #e5e7eb;
107
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
108
+ cursor: pointer;
109
+ transition: all 0.25s ease;
110
+ }
111
+
112
+ .add-bar:hover {
113
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
114
+ transform: translateY(-2px);
115
+ }
116
+
117
+ .add-bar input {
118
+ border: none;
119
+ outline: none;
120
+ background: transparent;
121
+ font-size: 14px;
122
+ color: #6b7280;
123
+ pointer-events: none; /* prevents typing */
124
+ }
125
+
126
+ .plus {
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ width: 48px;
131
+ height: 38px;
132
+ border-radius: 8px;
133
+ background: linear-gradient(135deg, #ed9817, #eb9a0f);
134
+ color: white;
135
+ font-size: 18px;
136
+ font-weight: bold;
137
+ transition: transform 0.2s ease;
138
+ }
139
+
140
+ .add-bar:hover .plus {
141
+ transform: rotate(90deg);
142
+ }
143
+
144
+ /* Scrollable section */
145
+ .notes-scroll {
146
+ flex: 1;
147
+ overflow-y: auto;
148
+ padding-right: 4px;
149
+ min-height: 0;
150
+ }
151
+
152
+ /* Form spacing */
153
+ .note-form {
154
+ margin-bottom: 16px;
155
+ }
156
+
157
+ /* Saved notes style */
158
+ .note-card {
159
+ background: #f4f6f8;
160
+ padding: 4px;
161
+ border-radius: 10px;
162
+ margin-bottom: 10px;
163
+
164
+ }
165
+
166
+ .reminder-wrapper {
167
+ position: relative;
168
+ }
169
+
170
+ .reminder-wrapper input {
171
+ cursor: pointer;
172
+ }
173
+
174
+ /* Dropdown */
175
+ .reminder-dropdown {
176
+ position: absolute;
177
+ top: 100%;
178
+ left: 0;
179
+ width: 100%;
180
+ background: white;
181
+ border: 1px solid #ddd;
182
+ border-radius: 10px;
183
+ margin-top: 4px;
184
+ box-shadow: 0 8px 20px rgba(0,0,0,0.08);
185
+ z-index: 10;
186
+ overflow: hidden;
187
+ }
188
+
189
+ .reminder-dropdown div {
190
+ padding: 10px;
191
+ font-size: 14px;
192
+ cursor: pointer;
193
+ transition: background 0.2s ease;
194
+ }
195
+
196
+ .reminder-dropdown div:hover {
197
+ background: #f3f4f6;
198
+ }
199
+ .custom-time {
200
+ padding: 10px;
201
+ border-top: 1px solid #eee;
202
+ }
203
+
204
+ .custom-time input {
205
+ width: 100%;
206
+ }
207
+
208
+ /* Note header layout */
209
+ .note-header {
210
+ display: flex;
211
+ justify-content: space-between;
212
+ align-items: center;
213
+ }
214
+
215
+ /* Action icons */
216
+ .note-actions {
217
+ display: flex;
218
+ gap: 10px;
219
+ }
220
+
221
+ .icon {
222
+ cursor: pointer;
223
+ font-size: 14px;
224
+ opacity: 0.6;
225
+ transition: 0.2s ease;
226
+ }
227
+
228
+ .icon:hover {
229
+ opacity: 1;
230
+ transform: scale(1.1);
231
+ }
232
+
233
+ .icon svg {
234
+ fill: currentColor;
235
+ }
236
+
237
+ /* Specific hover colors */
238
+ .icon.delete:hover {
239
+ color: #dc2626; /* red */
240
+ }
241
+
242
+ .icon.edit:hover {
243
+ color: #4f46e5; /* purple */
244
+ }
245
+
246
+ .reminder-text {
247
+ display: block;
248
+ margin-top: 6px;
249
+ font-size: 12px;
250
+ color: #f12011;
251
+ }
@@ -0,0 +1,107 @@
1
+ <div class="notes" >
2
+
3
+
4
+ <!-- Scrollable content -->
5
+ <div class="notes-scroll">
6
+
7
+ <div class="add-bar" *ngIf="!showForm" (click)="openForm()">
8
+ <input type="text" placeholder="Add New Note..." readonly />
9
+ <span class="plus">+</span>
10
+ </div>
11
+
12
+
13
+ <!-- Expandable form -->
14
+ <div *ngIf="showForm" class="note-form">
15
+
16
+ <input
17
+ type="text"
18
+ placeholder="Title"
19
+ [(ngModel)]="noteTitle"
20
+ />
21
+
22
+ <textarea
23
+ placeholder="Write Your Note Here..."
24
+ [(ngModel)]="noteContent">
25
+ </textarea>
26
+ <div class="reminder-section">
27
+
28
+ <div class="reminder-wrapper">
29
+
30
+ <input
31
+ type="text"
32
+ placeholder="Add reminder time"
33
+ [value]="displayReminder"
34
+ readonly
35
+ (click)="toggleReminderDropdown()"
36
+ />
37
+
38
+ <div class="reminder-dropdown" *ngIf="showReminderDropdown">
39
+ <div (click)="setQuickReminder(30)">30 minutes</div>
40
+ <div (click)="setQuickReminder(60)">1 hour</div>
41
+ <div (click)="setQuickReminder(120)">2 hours</div>
42
+
43
+
44
+
45
+ <div class="custom-option" (click)="showCustomInput = true">
46
+ Custom time
47
+ </div>
48
+
49
+ <!-- Custom datetime picker -->
50
+ <div *ngIf="showCustomInput" class="custom-time">
51
+ <input
52
+ placeholder="Set custom time"
53
+ type="datetime-local"
54
+ [(ngModel)]="reminderTime"
55
+ (change)="setCustomReminder()"
56
+ />
57
+ </div>
58
+
59
+ <div
60
+ *ngIf="reminderTime"
61
+ class="remove-option"
62
+ (click)="clearReminder()">
63
+ None
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ <div class="form-actions">
69
+ <button type="button" class="cancel-btn" (click)="cancel()">Cancel</button>
70
+ <button type="button" class="save-btn" (click)="save()">Save Note</button>
71
+ </div>
72
+ </div>
73
+
74
+ <div class="existing-notes" *ngIf="selectedNotes.length > 0">
75
+ <h5>Saved Notes:-</h5>
76
+
77
+
78
+ <div class="note-card" *ngFor="let note of selectedNotes; let i = index">
79
+ <div class="note-header">
80
+ <strong>{{ note.title }}</strong>
81
+
82
+ <div class="note-actions">
83
+ <span class="icon edit" (click)="editNote(i)">
84
+ <svg viewBox="0 0 24 24" width="18" height="18">
85
+ <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25z"/>
86
+ <path d="M20.71 7.04a1 1 0 000-1.41L18.37 3.29a1 1 0 00-1.41 0l-1.83 1.83
87
+ 3.75 3.75 1.83-1.83z"/>
88
+ </svg>
89
+ </span>
90
+ <span class="icon delete" (click)="deleteNote(i)">
91
+ <svg viewBox="0 0 24 24" width="18" height="18">
92
+ <path d="M6 7h12v2H6zm2 3h8v10H8zm3-7h2v2h-2z"/>
93
+ </svg>
94
+ </span>
95
+ </div>
96
+ </div>
97
+ <p>{{ note.content }}</p>
98
+
99
+ <small *ngIf="note.reminder_at && getTimeLeft(note.reminder_at)" class="reminder-text">
100
+ ⏳ {{ getTimeLeft(note.reminder_at) }}
101
+ </small>
102
+
103
+ </div>
104
+ </div>
105
+
106
+ </div>
107
+ </div>
@@ -0,0 +1,23 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { Notes } from './notes';
4
+
5
+ describe('Notes', () => {
6
+ let component: Notes;
7
+ let fixture: ComponentFixture<Notes>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [Notes]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(Notes);
16
+ component = fixture.componentInstance;
17
+ await fixture.whenStable();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
@@ -0,0 +1,328 @@
1
+ import { ChangeDetectorRef, Component, EventEmitter, Input, NgZone, OnChanges, OnInit, Output } from '@angular/core';
2
+ import { FormsModule } from '@angular/forms';
3
+ import { CommonModule } from '@angular/common';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import { ReminderService } from '../../services/notification';
6
+
7
+ @Component({
8
+ selector: 'app-notes',
9
+ standalone: true,
10
+ imports: [FormsModule, CommonModule],
11
+ templateUrl: './notes.html',
12
+ styleUrls: ['./notes.css']
13
+ })
14
+ export class Notes implements OnChanges, OnInit {
15
+
16
+ @Input() selectedDate: string | null = null;
17
+ @Output() notesChanged = new EventEmitter<{ [key: string]: any[] }>();
18
+
19
+ noteTitle = '';
20
+ noteContent = '';
21
+ notes: { [key: string]: any[] } = {};
22
+ selectedNotes: any[] = [];
23
+ userId!: string;
24
+
25
+ reminderTime: string | null = null;
26
+ showReminderDropdown = false;
27
+ displayReminder = '';
28
+ showCustomInput = false;
29
+ editingIndex: number | null = null;
30
+ editingNoteId: string | null = null;
31
+ showForm = false;
32
+
33
+ constructor(
34
+ private ngZone: NgZone,
35
+ private cd: ChangeDetectorRef,
36
+ private reminderService: ReminderService
37
+ ) {
38
+ let storedId = localStorage.getItem('testUserId');
39
+ if (!storedId) {
40
+ storedId = uuidv4();
41
+ localStorage.setItem('testUserId', storedId);
42
+ }
43
+ this.userId = storedId;
44
+ }
45
+
46
+ ngOnInit() {
47
+ this.reminderService.requestPermission();
48
+ this.listenToHost();
49
+ this.loadNotesFromHost();
50
+
51
+ this.reminderService.timeTick$.subscribe(() => {
52
+ this.cd.detectChanges();
53
+ });
54
+
55
+ }
56
+
57
+ // =========================
58
+ // HOST MESSAGE LISTENER
59
+ // =========================
60
+ listenToHost() {
61
+ window.addEventListener('message', (event: MessageEvent) => {
62
+ //if (event.source !== window.parent) return;
63
+ const msg = event.data;
64
+ if (!msg || !msg.type) return;
65
+
66
+ this.ngZone.run(() => {
67
+ switch (msg.type) {
68
+ case 'USER_LOGIN':
69
+ this.userId = msg.userId;
70
+ this.loadNotesFromHost();
71
+ break;
72
+ case 'NOTES_LOADED':
73
+ if (!Array.isArray(msg.notes)) return;
74
+ this.handleNotesLoaded(msg.notes);
75
+ break;
76
+ case 'NOTE_SAVED':
77
+ this.handleNoteSaved(msg.note);
78
+ break;
79
+ case 'NOTE_UPDATED':
80
+ this.handleNoteUpdated(msg.note);
81
+ break;
82
+ case 'NOTE_DELETED':
83
+ this.handleNoteDeleted(msg.id);
84
+ break;
85
+ }
86
+ this.cd.detectChanges();
87
+ });
88
+ });
89
+ }
90
+
91
+ // =========================
92
+ // HANDLE NOTES
93
+ // =========================
94
+ handleNotesLoaded(data: any[]) {
95
+
96
+ const grouped: { [key: string]: any[] } = {};
97
+
98
+ data.forEach(note => {
99
+
100
+ const dateKey = note.note_date.split('T')[0];
101
+
102
+ if (!grouped[dateKey]) {
103
+ grouped[dateKey] = [];
104
+ }
105
+
106
+ grouped[dateKey].push(note);
107
+
108
+ this.reminderService.addReminder(note);
109
+
110
+ });
111
+
112
+ this.notes = grouped;
113
+
114
+ // send to panel for calendar indicator
115
+ this.notesChanged.emit(this.notes);
116
+
117
+ // IMPORTANT: show notes for current selected date
118
+ if (this.selectedDate && this.notes[this.selectedDate]) {
119
+ this.selectedNotes = [...this.notes[this.selectedDate]];
120
+ } else {
121
+ this.selectedNotes = [];
122
+ }
123
+
124
+ }
125
+ handleNoteSaved(note: any) {
126
+
127
+ const dateKey = note.note_date.split('T')[0];
128
+
129
+ if (!this.notes[dateKey]) {
130
+ this.notes[dateKey] = [];
131
+ }
132
+
133
+ this.notes[dateKey].push(note);
134
+
135
+ // refresh object reference
136
+ this.notes = { ...this.notes };
137
+
138
+ // update currently visible notes
139
+ if (this.selectedDate === dateKey) {
140
+ this.selectedNotes = [...this.notes[dateKey]];
141
+ }
142
+ this.reminderService.addReminder(note);
143
+ // update calendar indicator
144
+ this.notesChanged.emit(this.notes);
145
+
146
+ this.resetForm();
147
+
148
+ }
149
+ handleNoteUpdated(note: any) {
150
+ if (!note || !note.note_date) return;
151
+ const dateKey = note.note_date;
152
+ const index = this.notes[dateKey]?.findIndex(n => n.id === note.id);
153
+ if (index !== undefined && index !== -1) this.notes[dateKey][index] = note;
154
+ this.selectedNotes = this.notes[this.selectedDate || dateKey] || [];
155
+ this.resetForm();
156
+ }
157
+
158
+ handleNoteDeleted(id: string) {
159
+ for (const dateKey in this.notes) {
160
+ this.notes[dateKey] = this.notes[dateKey].filter(n => n.id !== id);
161
+ }
162
+ this.selectedNotes = this.notes[this.selectedDate || ''] || [];
163
+ this.reminderService.removeReminder(id);
164
+ }
165
+
166
+ // =========================
167
+ // LOAD NOTES
168
+ // =========================
169
+ loadNotesFromHost() {
170
+ window.parent.postMessage({ type: 'LOAD_NOTES', userId: this.userId }, '*');
171
+ }
172
+
173
+ // =========================
174
+ // NG ON CHANGES
175
+ // =========================
176
+ ngOnChanges() {
177
+
178
+ if (this.selectedDate) {
179
+ this.selectedNotes = this.notes[this.selectedDate] || [];
180
+ } else {
181
+ this.selectedNotes = [];
182
+ }
183
+
184
+ this.resetForm();
185
+
186
+ }
187
+ // =========================
188
+ // FORM
189
+ // =========================
190
+ openForm() {
191
+ this.showForm = true;
192
+ }
193
+
194
+ cancel() {
195
+ this.resetForm();
196
+ }
197
+
198
+ resetForm() {
199
+ this.showForm = false;
200
+ this.noteTitle = '';
201
+ this.noteContent = '';
202
+ this.reminderTime = null;
203
+ this.displayReminder = '';
204
+ this.showReminderDropdown = false;
205
+ this.showCustomInput = false;
206
+ this.editingIndex = null;
207
+ this.editingNoteId = null;
208
+ }
209
+
210
+ // =========================
211
+ // REMINDERS
212
+ // =========================
213
+ toggleReminderDropdown() {
214
+ this.showReminderDropdown = !this.showReminderDropdown;
215
+ }
216
+
217
+ setQuickReminder(minutes: number) {
218
+
219
+ const now = new Date();
220
+ now.setMinutes(now.getMinutes() + minutes);
221
+
222
+ this.reminderTime = now.toISOString(); // keep UTC
223
+
224
+ if (minutes === 30) this.displayReminder = 'Reminder in 30 minutes';
225
+ if (minutes === 60) this.displayReminder = 'Reminder in 1 hour';
226
+ if (minutes === 120) this.displayReminder = 'Reminder in 2 hours';
227
+
228
+ this.closeDropdown();
229
+ }
230
+
231
+ setCustomReminder() {
232
+ if (!this.reminderTime) return;
233
+ const selected = new Date(this.reminderTime);
234
+ this.displayReminder = `Reminder on ${selected.toLocaleString()}`;
235
+ this.closeDropdown();
236
+ }
237
+
238
+ closeDropdown() {
239
+ this.showReminderDropdown = false;
240
+ this.showCustomInput = false;
241
+ }
242
+
243
+ clearReminder() {
244
+ this.reminderTime = null;
245
+ this.displayReminder = '';
246
+ }
247
+
248
+ // =========================
249
+ // SAVE NOTE
250
+ // =========================
251
+ save() {
252
+ if (!this.noteTitle || !this.selectedDate) return;
253
+
254
+ const payload: any = {
255
+ user_id: this.userId,
256
+ note_date: this.selectedDate,
257
+ title: this.noteTitle,
258
+ content: this.noteContent || '',
259
+ reminder_at: this.reminderTime
260
+ ? new Date(this.reminderTime).toISOString()
261
+ : null
262
+ };
263
+
264
+ if (this.editingNoteId) payload.id = this.editingNoteId;
265
+
266
+ const type = this.editingNoteId ? 'UPDATE_NOTE' : 'SAVE_NOTE';
267
+
268
+ // Send userId consistently
269
+ window.parent.postMessage({ type, userId: this.userId, payload }, '*');
270
+ }
271
+
272
+ // =========================
273
+ // EDIT / DELETE
274
+ // =========================
275
+ editNote(index: number) {
276
+ const note = this.selectedNotes[index];
277
+ this.noteTitle = note.title;
278
+ this.noteContent = note.content;
279
+ this.reminderTime = note.reminder_at || null;
280
+ this.displayReminder = note.reminder_at ? `Reminder on ${new Date(note.reminder_at).toLocaleString()}` : '';
281
+ this.editingIndex = index;
282
+ this.editingNoteId = note.id;
283
+ this.showForm = true;
284
+ }
285
+
286
+ deleteNote(index: number) {
287
+ const note = this.selectedNotes[index];
288
+ window.parent.postMessage({ type: 'DELETE_NOTE', id: note.id, userId: this.userId }, '*');
289
+ }
290
+
291
+ // =========================
292
+ // TIMER
293
+ // =========================
294
+ getTimeLeft(reminderAt: string): string | null {
295
+
296
+ if (!reminderAt) return null;
297
+
298
+ const now = new Date().getTime();
299
+ const reminderTime = new Date(reminderAt).getTime();
300
+
301
+ const diff = reminderTime - now;
302
+
303
+ if (diff <= 0) return 'expired';
304
+
305
+ const totalSeconds = Math.floor(diff / 1000);
306
+
307
+ const days = Math.floor(totalSeconds / 86400);
308
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
309
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
310
+ const seconds = totalSeconds % 60;
311
+
312
+ if (days > 0) {
313
+ return `${days}d ${hours}h ${minutes}m ${seconds}s left`;
314
+ }
315
+
316
+ if (hours > 0) {
317
+ return `${hours}h ${minutes}m ${seconds}s left`;
318
+ }
319
+
320
+ if (minutes > 0) {
321
+ return `${minutes}m ${seconds}s left`;
322
+ }
323
+
324
+ return `${seconds}s left`;
325
+ }
326
+
327
+
328
+ }