iobroker.kanban 0.3.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +224 -0
  3. package/admin/i18n/de.json +100 -0
  4. package/admin/i18n/en.json +100 -0
  5. package/admin/i18n/es.json +100 -0
  6. package/admin/i18n/fr.json +100 -0
  7. package/admin/i18n/it.json +100 -0
  8. package/admin/i18n/nl.json +100 -0
  9. package/admin/i18n/pl.json +100 -0
  10. package/admin/i18n/pt.json +100 -0
  11. package/admin/i18n/ru.json +100 -0
  12. package/admin/i18n/uk.json +100 -0
  13. package/admin/i18n/zh-cn.json +100 -0
  14. package/admin/jsonConfig.json +668 -0
  15. package/admin/kanban.png +0 -0
  16. package/io-package.json +299 -0
  17. package/lib/cron.js +203 -0
  18. package/lib/dueat.js +77 -0
  19. package/lib/events.js +24 -0
  20. package/lib/holidays.js +167 -0
  21. package/lib/i18n-server.js +301 -0
  22. package/lib/notify.js +627 -0
  23. package/lib/scheduler.js +198 -0
  24. package/lib/server.js +799 -0
  25. package/lib/store.js +1500 -0
  26. package/main.js +382 -0
  27. package/package.json +58 -0
  28. package/www/css/app.css +1022 -0
  29. package/www/i18n/de.json +238 -0
  30. package/www/i18n/en.json +238 -0
  31. package/www/i18n/es.json +238 -0
  32. package/www/i18n/fr.json +238 -0
  33. package/www/i18n/it.json +238 -0
  34. package/www/i18n/nl.json +238 -0
  35. package/www/i18n/pl.json +238 -0
  36. package/www/i18n/pt.json +238 -0
  37. package/www/i18n/ru.json +238 -0
  38. package/www/i18n/uk.json +238 -0
  39. package/www/i18n/zh-cn.json +238 -0
  40. package/www/index.html +171 -0
  41. package/www/js/api.js +98 -0
  42. package/www/js/app.js +411 -0
  43. package/www/js/board.js +983 -0
  44. package/www/js/colorpicker.js +165 -0
  45. package/www/js/dialogs.js +1419 -0
  46. package/www/js/i18n.js +43 -0
  47. package/www/vendor/marked.min.js +69 -0
  48. package/www/vendor/purify.min.js +3 -0
  49. package/www/vendor/sortable.min.js +2 -0
@@ -0,0 +1,198 @@
1
+ 'use strict';
2
+
3
+ const { todayStr } = require('./store');
4
+
5
+ /** Aktuelle Ortszeit als HH:MM. */
6
+ function nowHHMM() {
7
+ const now = new Date();
8
+ return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
9
+ }
10
+
11
+ /**
12
+ * Fälligkeits-/Erinnerungs-Check.
13
+ * 60-s-Tick: aktualisiert Spiegel-States; wenn die Uhrzeit reminderTime (HH:MM)
14
+ * erreicht ist, werden für fällige Karten cardDue-Events emittiert.
15
+ * Zusätzlich ein Lauf 30 s nach Adapterstart (falls reminderTime verpasst wurde,
16
+ * werden nur Karten erinnert, deren lastReminderAt nicht heute ist).
17
+ */
18
+ class Scheduler {
19
+ constructor(adapter, store, bus) {
20
+ this.adapter = adapter;
21
+ this.store = store;
22
+ this.bus = bus;
23
+ this._interval = null;
24
+ this._startupTimer = null;
25
+ this._lastMinute = '';
26
+ this._lastCleanupDay = '';
27
+ }
28
+
29
+ start() {
30
+ this._interval = this.adapter.setInterval(() => this._tick(), 60 * 1000);
31
+ this._startupTimer = this.adapter.setTimeout(() => {
32
+ this._runDueCheck(true).catch(e => this.adapter.log.error(`Due check: ${e.message}`));
33
+ this._runCleanup().catch(e => this.adapter.log.error(`Cleanup: ${e.message}`));
34
+ // Uhrzeit-Fälligkeiten des laufenden Tages nachholen, die während eines
35
+ // Neustarts oder einer Ausfallzeit verpasst wurden
36
+ this._runExactDueCheck(nowHHMM()).catch(e => this.adapter.log.error(`Exact due check: ${e.message}`));
37
+ }, 30 * 1000);
38
+ }
39
+
40
+ stop() {
41
+ if (this._interval) {
42
+ this.adapter.clearInterval(this._interval);
43
+ }
44
+ if (this._startupTimer) {
45
+ this.adapter.clearTimeout(this._startupTimer);
46
+ }
47
+ this._interval = null;
48
+ this._startupTimer = null;
49
+ }
50
+
51
+ _tick() {
52
+ this.store.updateMirrors().catch(e => this.adapter.log.error(`Mirror states: ${e.message}`));
53
+ const hhmm = nowHHMM();
54
+ if (hhmm === this._lastMinute) {
55
+ return;
56
+ }
57
+ this._lastMinute = hhmm;
58
+ if (hhmm === (this.adapter.config.reminderTime || '08:00')) {
59
+ this._runDueCheck(false).catch(e => this.adapter.log.error(`Due check: ${e.message}`));
60
+ }
61
+ // Kartengenaue Fälligkeit (optional): jede Minute prüfen
62
+ this._runExactDueCheck(hhmm).catch(e => this.adapter.log.error(`Exact due check: ${e.message}`));
63
+ // Aufräumen einmal täglich (beim ersten Tick eines neuen Kalendertags)
64
+ const day = todayStr();
65
+ if (day !== this._lastCleanupDay) {
66
+ this._runCleanup().catch(e => this.adapter.log.error(`Cleanup: ${e.message}`));
67
+ }
68
+ }
69
+
70
+ /** Automatischen Aufräumlauf ausführen und die gebündelte E-Mail-Zusammenfassung verschicken. */
71
+ async _runCleanup() {
72
+ this._lastCleanupDay = todayStr();
73
+ const results = this.store.runCleanup(Date.now());
74
+ const moved = results.reduce((n, r) => n + r.moved.length, 0);
75
+ const purged = results.reduce((n, r) => n + r.purged.length, 0);
76
+ if (moved || purged) {
77
+ this.adapter.log.info(`Cleanup: ${moved} card(s) moved to the trash, ${purged} permanently removed`);
78
+ if (this.adapter.notifier && this.adapter.notifier.sendCleanupSummary) {
79
+ await this.adapter.notifier.sendCleanupSummary(results);
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Kartengenaue Fälligkeit (Instanz-Option „Fällig-Ereignis zur Uhrzeit").
86
+ * Für Karten mit gesetzter Uhrzeit wird ein zusätzliches cardDue-Ereignis mit
87
+ * `detail.exact = true` ausgelöst, sobald die Uhrzeit erreicht ist – unabhängig
88
+ * von der täglichen Erinnerungs-Uhrzeit. `lastExactAt` (Datum) verhindert
89
+ * Wiederholungen und holt eine verpasste Minute (z. B. Adapterstart) am
90
+ * gleichen Tag nach.
91
+ *
92
+ * @param hhmm aktuelle Uhrzeit als HH:MM
93
+ */
94
+ async _runExactDueCheck(hhmm) {
95
+ if (!this.adapter.config.dueExact) {
96
+ return;
97
+ }
98
+ const today = todayStr();
99
+ let count = 0;
100
+ for (const board of this.store.boards.values()) {
101
+ let touched = false;
102
+ for (const card of board.cards) {
103
+ if (!card.due || !card.dueTime) {
104
+ continue;
105
+ }
106
+ if (card.due !== today || card.dueTime > hhmm) {
107
+ continue;
108
+ }
109
+ if (card.lastExactAt === today) {
110
+ continue;
111
+ }
112
+ if (this.store._isDoneColumn(board, card.columnId)) {
113
+ continue;
114
+ }
115
+ if (this.store._isTrashColumn(board, card.columnId)) {
116
+ continue;
117
+ }
118
+ card.lastExactAt = today;
119
+ touched = true;
120
+ count++;
121
+ this.bus.emitEvent('cardDue', {
122
+ board: { id: board.id, title: board.title },
123
+ card,
124
+ detail: { due: card.due, dueTime: card.dueTime, exact: true, overdue: false },
125
+ });
126
+ }
127
+ if (touched) {
128
+ board.rev++;
129
+ this.store._schedulePersist(board.id);
130
+ if (this.store.onChange) {
131
+ this.store.onChange(board.id, board.rev);
132
+ }
133
+ }
134
+ }
135
+ if (count) {
136
+ this.adapter.log.info(`Exact due check: ${count} event(s) fired`);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * @param startup true = Lauf nach Adapterstart: nur nachholen, wenn reminderTime
142
+ * heute schon vorbei ist (sonst würde die Erinnerung zu früh kommen)
143
+ */
144
+ async _runDueCheck(startup) {
145
+ const cfg = this.adapter.config;
146
+ if (startup) {
147
+ const now = new Date();
148
+ const hhmm = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
149
+ if (hhmm < (cfg.reminderTime || '08:00')) {
150
+ return;
151
+ }
152
+ }
153
+ const daysBefore = Number(cfg.reminderDaysBefore) || 0;
154
+ const threshold = new Date();
155
+ threshold.setDate(threshold.getDate() + daysBefore);
156
+ const thresholdStr = `${threshold.getFullYear()}-${String(threshold.getMonth() + 1).padStart(2, '0')}-${String(threshold.getDate()).padStart(2, '0')}`;
157
+ const today = todayStr();
158
+
159
+ let count = 0;
160
+ for (const board of this.store.boards.values()) {
161
+ let touched = false;
162
+ for (const card of board.cards) {
163
+ if (!card.due || card.due > thresholdStr) {
164
+ continue;
165
+ }
166
+ if (this.store._isDoneColumn(board, card.columnId)) {
167
+ continue;
168
+ }
169
+ if (this.store._isTrashColumn(board, card.columnId)) {
170
+ continue;
171
+ }
172
+ if (card.lastReminderAt === today) {
173
+ continue;
174
+ }
175
+ card.lastReminderAt = today;
176
+ touched = true;
177
+ count++;
178
+ this.bus.emitEvent('cardDue', {
179
+ board: { id: board.id, title: board.title },
180
+ card,
181
+ detail: { due: card.due, overdue: card.due < today },
182
+ });
183
+ }
184
+ if (touched) {
185
+ board.rev++;
186
+ this.store._schedulePersist(board.id);
187
+ if (this.store.onChange) {
188
+ this.store.onChange(board.id, board.rev);
189
+ }
190
+ }
191
+ }
192
+ if (count) {
193
+ this.adapter.log.info(`Due check: ${count} reminder(s) fired`);
194
+ }
195
+ }
196
+ }
197
+
198
+ module.exports = { Scheduler };