@mrck-labs/vanaheim-shared 0.1.1 → 0.2.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.
package/dist/index.js CHANGED
@@ -36,29 +36,72 @@ __export(src_exports, {
36
36
  LINEAR_PRIORITY_COLORS: () => LINEAR_PRIORITY_COLORS,
37
37
  LINEAR_PRIORITY_LABELS: () => LINEAR_PRIORITY_LABELS,
38
38
  SETTING_KEYS: () => SETTING_KEYS,
39
+ activeSessionAtom: () => activeSessionAtom,
40
+ addDays: () => addDays,
39
41
  calculateFocusStats: () => calculateFocusStats,
40
42
  calculateLieuBalance: () => calculateLieuBalance,
41
43
  calculateMonthlyExpenses: () => calculateMonthlyExpenses,
42
44
  calculateMonthlyIncome: () => calculateMonthlyIncome,
43
45
  calculateMonthlySavings: () => calculateMonthlySavings,
44
46
  calculateSavingsRate: () => calculateSavingsRate,
47
+ elapsedSecondsAtom: () => elapsedSecondsAtom,
45
48
  formatCurrency: () => formatCurrency,
46
49
  formatDate: () => formatDate,
50
+ formatDateHeader: () => formatDateHeader,
51
+ formatDateLocalized: () => formatDateLocalized,
52
+ formatDateLong: () => formatDateLong,
53
+ formatDateString: () => formatDateString,
47
54
  formatDueDate: () => formatDueDate,
55
+ formatDueDateString: () => formatDueDateString,
56
+ formatFullDate: () => formatFullDate,
57
+ formatMonthYear: () => formatMonthYear,
58
+ formatRelativeDueDate: () => formatRelativeDueDate,
59
+ formatRelativePayDate: () => formatRelativePayDate,
48
60
  formatRelativeTime: () => formatRelativeTime,
61
+ formatRelativeTimeExtended: () => formatRelativeTimeExtended,
49
62
  formatTime: () => formatTime,
63
+ formatTimeHHMM: () => formatTimeHHMM,
64
+ formatTimeLocalized: () => formatTimeLocalized,
65
+ formatTimeWithSeconds: () => formatTimeWithSeconds,
50
66
  formatTotalTime: () => formatTotalTime,
67
+ formatWeekRange: () => formatWeekRange,
68
+ formattedElapsedAtom: () => formattedElapsedAtom,
69
+ formattedRemainingAtom: () => formattedRemainingAtom,
51
70
  generateId: () => generateId,
52
71
  generateRandomColor: () => generateRandomColor,
53
72
  generateShortId: () => generateShortId,
73
+ getEndOfDayISO: () => getEndOfDayISO,
74
+ getNextWeek: () => getNextWeek,
75
+ getPreviousWeek: () => getPreviousWeek,
54
76
  getRepoName: () => getRepoName,
77
+ getStartOfDayISO: () => getStartOfDayISO,
78
+ getTodayMidnight: () => getTodayMidnight,
79
+ getTodayString: () => getTodayString,
80
+ getTomorrowString: () => getTomorrowString,
81
+ getWeekEnd: () => getWeekEnd,
82
+ getWeekEndString: () => getWeekEndString,
83
+ getWeekStart: () => getWeekStart,
84
+ getWeekStartString: () => getWeekStartString,
85
+ getYesterdayString: () => getYesterdayString,
86
+ isDueSoon: () => isDueSoon,
55
87
  isNonEmptyString: () => isNonEmptyString,
88
+ isOverdue: () => isOverdue,
56
89
  isPositiveNumber: () => isPositiveNumber,
90
+ isToday: () => isToday,
57
91
  isValidCurrency: () => isValidCurrency,
58
92
  isValidEmail: () => isValidEmail,
59
93
  isValidFrequency: () => isValidFrequency,
60
94
  isValidISODate: () => isValidISODate,
61
95
  isValidUrl: () => isValidUrl,
96
+ normalizeToMidnight: () => normalizeToMidnight,
97
+ parseLocalDate: () => parseLocalDate,
98
+ progressAtom: () => progressAtom,
99
+ progressPercentAtom: () => progressPercentAtom,
100
+ queryKeys: () => queryKeys,
101
+ remainingSecondsAtom: () => remainingSecondsAtom,
102
+ subtractDays: () => subtractDays,
103
+ targetSecondsAtom: () => targetSecondsAtom,
104
+ timerStatusAtom: () => timerStatusAtom,
62
105
  toMonthlyAmount: () => toMonthlyAmount,
63
106
  toYearlyAmount: () => toYearlyAmount,
64
107
  truncate: () => truncate
@@ -179,7 +222,7 @@ function formatTotalTime(seconds) {
179
222
  }
180
223
  return `${mins}m`;
181
224
  }
182
- function formatCurrency(amount, currency, locale = "en-CH") {
225
+ function formatCurrency(amount, currency, locale = "de-CH") {
183
226
  return new Intl.NumberFormat(locale, {
184
227
  style: "currency",
185
228
  currency,
@@ -216,12 +259,12 @@ function formatDueDate(dueDate) {
216
259
  tomorrow.setDate(tomorrow.getDate() + 1);
217
260
  const dueDay = new Date(due);
218
261
  dueDay.setHours(0, 0, 0, 0);
219
- const isOverdue = dueDay < today;
262
+ const isOverdue2 = dueDay < today;
220
263
  if (dueDay.getTime() === today.getTime()) {
221
264
  return { text: "Today", isOverdue: false };
222
265
  } else if (dueDay.getTime() === tomorrow.getTime()) {
223
266
  return { text: "Tomorrow", isOverdue: false };
224
- } else if (isOverdue) {
267
+ } else if (isOverdue2) {
225
268
  const daysAgo = Math.ceil(
226
269
  (today.getTime() - dueDay.getTime()) / (1e3 * 60 * 60 * 24)
227
270
  );
@@ -363,6 +406,517 @@ function calculateFocusStats(sessions) {
363
406
  completionRate
364
407
  };
365
408
  }
409
+
410
+ // src/query/index.ts
411
+ var queryKeys = {
412
+ // -------------------------------------------------------------------------
413
+ // Expenses
414
+ // -------------------------------------------------------------------------
415
+ expenses: {
416
+ all: ["expenses"],
417
+ lists: () => [...queryKeys.expenses.all, "list"],
418
+ list: (filters) => [...queryKeys.expenses.lists(), filters],
419
+ detail: (id) => [...queryKeys.expenses.all, "detail", id]
420
+ },
421
+ // Expense Categories
422
+ expenseCategories: {
423
+ all: ["expenseCategories"],
424
+ list: () => [...queryKeys.expenseCategories.all, "list"]
425
+ },
426
+ // Expense Payments
427
+ expensePayments: {
428
+ all: ["expensePayments"],
429
+ lists: () => [...queryKeys.expensePayments.all, "list"],
430
+ list: (filters) => [...queryKeys.expensePayments.lists(), filters],
431
+ detail: (id) => [...queryKeys.expensePayments.all, "detail", id]
432
+ },
433
+ // -------------------------------------------------------------------------
434
+ // Income
435
+ // -------------------------------------------------------------------------
436
+ incomes: {
437
+ all: ["incomes"],
438
+ lists: () => [...queryKeys.incomes.all, "list"],
439
+ list: (filters) => [...queryKeys.incomes.lists(), filters],
440
+ detail: (id) => [...queryKeys.incomes.all, "detail", id]
441
+ },
442
+ // Income Categories
443
+ incomeCategories: {
444
+ all: ["incomeCategories"],
445
+ list: () => [...queryKeys.incomeCategories.all, "list"]
446
+ },
447
+ // Income Payments
448
+ incomePayments: {
449
+ all: ["incomePayments"],
450
+ lists: () => [...queryKeys.incomePayments.all, "list"],
451
+ list: (filters) => [...queryKeys.incomePayments.lists(), filters],
452
+ detail: (id) => [...queryKeys.incomePayments.all, "detail", id]
453
+ },
454
+ // Exchange Rates
455
+ exchangeRates: {
456
+ all: ["exchangeRates"],
457
+ list: () => [...queryKeys.exchangeRates.all, "list"]
458
+ },
459
+ // -------------------------------------------------------------------------
460
+ // Focus
461
+ // -------------------------------------------------------------------------
462
+ focusSessions: {
463
+ all: ["focusSessions"],
464
+ lists: () => [...queryKeys.focusSessions.all, "list"],
465
+ list: (filters) => [...queryKeys.focusSessions.lists(), filters],
466
+ today: () => [...queryKeys.focusSessions.all, "today"],
467
+ active: () => [...queryKeys.focusSessions.all, "active"],
468
+ detail: (id) => [...queryKeys.focusSessions.all, "detail", id]
469
+ },
470
+ // Focus Categories
471
+ focusCategories: {
472
+ all: ["focusCategories"],
473
+ list: () => [...queryKeys.focusCategories.all, "list"]
474
+ },
475
+ // -------------------------------------------------------------------------
476
+ // Settings
477
+ // -------------------------------------------------------------------------
478
+ settings: {
479
+ all: ["settings"],
480
+ detail: (key) => [...queryKeys.settings.all, key]
481
+ },
482
+ // -------------------------------------------------------------------------
483
+ // EF Work (Lieu Days & Links)
484
+ // -------------------------------------------------------------------------
485
+ lieuDays: {
486
+ all: ["lieuDays"],
487
+ list: () => [...queryKeys.lieuDays.all, "list"],
488
+ balance: () => [...queryKeys.lieuDays.all, "balance"],
489
+ detail: (id) => [...queryKeys.lieuDays.all, "detail", id]
490
+ },
491
+ efLinks: {
492
+ all: ["efLinks"],
493
+ list: () => [...queryKeys.efLinks.all, "list"],
494
+ detail: (id) => [...queryKeys.efLinks.all, "detail", id]
495
+ },
496
+ // -------------------------------------------------------------------------
497
+ // Banking
498
+ // -------------------------------------------------------------------------
499
+ bankConnections: {
500
+ all: ["bankConnections"],
501
+ list: () => [...queryKeys.bankConnections.all, "list"],
502
+ detail: (id) => [...queryKeys.bankConnections.all, "detail", id]
503
+ },
504
+ bankTransactions: {
505
+ all: ["bankTransactions"],
506
+ lists: () => [...queryKeys.bankTransactions.all, "list"],
507
+ list: (filters) => [...queryKeys.bankTransactions.lists(), filters],
508
+ stats: (connectionId) => [...queryKeys.bankTransactions.all, "stats", connectionId]
509
+ },
510
+ // -------------------------------------------------------------------------
511
+ // Health
512
+ // -------------------------------------------------------------------------
513
+ healthCategories: {
514
+ all: ["healthCategories"],
515
+ list: () => [...queryKeys.healthCategories.all, "list"],
516
+ detail: (id) => [...queryKeys.healthCategories.all, "detail", id]
517
+ },
518
+ healthHabits: {
519
+ all: ["healthHabits"],
520
+ lists: () => [...queryKeys.healthHabits.all, "list"],
521
+ list: (filters) => [...queryKeys.healthHabits.lists(), filters],
522
+ detail: (id) => [...queryKeys.healthHabits.all, "detail", id]
523
+ },
524
+ healthCompletions: {
525
+ all: ["healthCompletions"],
526
+ lists: () => [...queryKeys.healthCompletions.all, "list"],
527
+ list: (filters) => [...queryKeys.healthCompletions.lists(), filters],
528
+ forHabit: (habitId) => [...queryKeys.healthCompletions.all, "habit", habitId],
529
+ forDate: (date) => [...queryKeys.healthCompletions.all, "date", date]
530
+ },
531
+ healthStats: {
532
+ all: ["healthStats"],
533
+ forDate: (date) => [...queryKeys.healthStats.all, date ?? "today"]
534
+ },
535
+ // -------------------------------------------------------------------------
536
+ // Prompts
537
+ // -------------------------------------------------------------------------
538
+ promptCategories: {
539
+ all: ["promptCategories"],
540
+ list: () => [...queryKeys.promptCategories.all, "list"],
541
+ detail: (id) => [...queryKeys.promptCategories.all, "detail", id]
542
+ },
543
+ promptLabels: {
544
+ all: ["promptLabels"],
545
+ list: () => [...queryKeys.promptLabels.all, "list"],
546
+ detail: (id) => [...queryKeys.promptLabels.all, "detail", id]
547
+ },
548
+ prompts: {
549
+ all: ["prompts"],
550
+ lists: () => [...queryKeys.prompts.all, "list"],
551
+ list: (filters) => [...queryKeys.prompts.lists(), filters],
552
+ detail: (id) => [...queryKeys.prompts.all, "detail", id]
553
+ },
554
+ // -------------------------------------------------------------------------
555
+ // Training
556
+ // -------------------------------------------------------------------------
557
+ trainingActivities: {
558
+ all: ["trainingActivities"],
559
+ list: () => [...queryKeys.trainingActivities.all, "list"],
560
+ detail: (id) => [...queryKeys.trainingActivities.all, "detail", id],
561
+ byStravaType: (stravaType) => [...queryKeys.trainingActivities.all, "strava", stravaType]
562
+ },
563
+ trainingSessions: {
564
+ all: ["trainingSessions"],
565
+ lists: () => [...queryKeys.trainingSessions.all, "list"],
566
+ list: (filters) => [...queryKeys.trainingSessions.lists(), filters],
567
+ detail: (id) => [...queryKeys.trainingSessions.all, "detail", id],
568
+ byStravaId: (stravaActivityId) => [...queryKeys.trainingSessions.all, "strava", stravaActivityId]
569
+ },
570
+ plannedSessions: {
571
+ all: ["plannedSessions"],
572
+ lists: () => [...queryKeys.plannedSessions.all, "list"],
573
+ list: (filters) => [...queryKeys.plannedSessions.lists(), filters],
574
+ detail: (id) => [...queryKeys.plannedSessions.all, "detail", id],
575
+ upcoming: () => [...queryKeys.plannedSessions.all, "upcoming"]
576
+ },
577
+ races: {
578
+ all: ["races"],
579
+ lists: () => [...queryKeys.races.all, "list"],
580
+ list: (filters) => [...queryKeys.races.lists(), filters],
581
+ detail: (id) => [...queryKeys.races.all, "detail", id],
582
+ next: () => [...queryKeys.races.all, "next"]
583
+ },
584
+ trainingStats: {
585
+ all: ["trainingStats"],
586
+ forRange: (dateFrom, dateTo) => [...queryKeys.trainingStats.all, dateFrom, dateTo]
587
+ },
588
+ // Strava
589
+ strava: {
590
+ all: ["strava"],
591
+ athlete: () => [...queryKeys.strava.all, "athlete"],
592
+ connected: () => [...queryKeys.strava.all, "connected"],
593
+ activities: (options) => [...queryKeys.strava.all, "activities", options]
594
+ },
595
+ // -------------------------------------------------------------------------
596
+ // Journal
597
+ // -------------------------------------------------------------------------
598
+ journalEntries: {
599
+ all: ["journalEntries"],
600
+ lists: () => [...queryKeys.journalEntries.all, "list"],
601
+ list: (filters) => [...queryKeys.journalEntries.lists(), filters],
602
+ forDate: (date) => [...queryKeys.journalEntries.all, "date", date]
603
+ },
604
+ // Journal data (aggregated)
605
+ journalData: {
606
+ all: ["journalData"],
607
+ forDate: (date) => [...queryKeys.journalData.all, date]
608
+ },
609
+ // -------------------------------------------------------------------------
610
+ // Chat
611
+ // -------------------------------------------------------------------------
612
+ chatConversations: {
613
+ all: ["chatConversations"],
614
+ list: () => [...queryKeys.chatConversations.all, "list"],
615
+ detail: (id) => [...queryKeys.chatConversations.all, "detail", id]
616
+ },
617
+ chatMessages: {
618
+ all: ["chatMessages"],
619
+ forConversation: (conversationId) => [...queryKeys.chatMessages.all, "conversation", conversationId]
620
+ },
621
+ // -------------------------------------------------------------------------
622
+ // Google Calendar
623
+ // -------------------------------------------------------------------------
624
+ googleCalendar: {
625
+ all: ["googleCalendar"],
626
+ calendars: () => [...queryKeys.googleCalendar.all, "calendars"],
627
+ events: (calendarId, timeMin, timeMax) => [...queryKeys.googleCalendar.all, "events", calendarId, timeMin, timeMax]
628
+ },
629
+ // -------------------------------------------------------------------------
630
+ // Linear
631
+ // -------------------------------------------------------------------------
632
+ linear: {
633
+ all: ["linear"],
634
+ issues: (filters) => [...queryKeys.linear.all, "issues", filters],
635
+ projects: () => [...queryKeys.linear.all, "projects"]
636
+ }
637
+ };
638
+
639
+ // src/atoms/focus.ts
640
+ var import_jotai = require("jotai");
641
+ var timerStatusAtom = (0, import_jotai.atom)("idle");
642
+ var targetSecondsAtom = (0, import_jotai.atom)(25 * 60);
643
+ var elapsedSecondsAtom = (0, import_jotai.atom)(0);
644
+ var activeSessionAtom = (0, import_jotai.atom)(null);
645
+ var remainingSecondsAtom = (0, import_jotai.atom)((get) => {
646
+ const target = get(targetSecondsAtom);
647
+ const elapsed = get(elapsedSecondsAtom);
648
+ return Math.max(0, target - elapsed);
649
+ });
650
+ var progressAtom = (0, import_jotai.atom)((get) => {
651
+ const target = get(targetSecondsAtom);
652
+ const elapsed = get(elapsedSecondsAtom);
653
+ if (target === 0) return 0;
654
+ return Math.min(1, elapsed / target);
655
+ });
656
+ var formattedRemainingAtom = (0, import_jotai.atom)((get) => {
657
+ return formatTime(get(remainingSecondsAtom));
658
+ });
659
+ var formattedElapsedAtom = (0, import_jotai.atom)((get) => {
660
+ return formatTime(get(elapsedSecondsAtom));
661
+ });
662
+ var progressPercentAtom = (0, import_jotai.atom)((get) => {
663
+ return get(progressAtom) * 100;
664
+ });
665
+
666
+ // src/date/index.ts
667
+ function normalizeToMidnight(date) {
668
+ const d = new Date(date);
669
+ d.setHours(0, 0, 0, 0);
670
+ return d;
671
+ }
672
+ function getTodayMidnight() {
673
+ return normalizeToMidnight(/* @__PURE__ */ new Date());
674
+ }
675
+ function formatDateString(date) {
676
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
677
+ }
678
+ function getTodayString() {
679
+ return formatDateString(/* @__PURE__ */ new Date());
680
+ }
681
+ function parseLocalDate(dateStr) {
682
+ return /* @__PURE__ */ new Date(dateStr + "T00:00:00");
683
+ }
684
+ function isToday(dateStr) {
685
+ if (!dateStr) return false;
686
+ return dateStr === getTodayString();
687
+ }
688
+ function isOverdue(dateStr) {
689
+ if (!dateStr) return false;
690
+ return dateStr < getTodayString();
691
+ }
692
+ function isDueSoon(dateStr, daysThreshold = 7) {
693
+ if (!dateStr) return false;
694
+ if (isOverdue(dateStr)) return false;
695
+ const today = getTodayMidnight();
696
+ const targetDate = parseLocalDate(dateStr);
697
+ const diffMs = targetDate.getTime() - today.getTime();
698
+ const diffDays = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
699
+ return diffDays >= 0 && diffDays <= daysThreshold;
700
+ }
701
+ function addDays(dateStr, days) {
702
+ const date = parseLocalDate(dateStr);
703
+ date.setDate(date.getDate() + days);
704
+ return formatDateString(date);
705
+ }
706
+ function subtractDays(dateStr, days) {
707
+ return addDays(dateStr, -days);
708
+ }
709
+ function getYesterdayString() {
710
+ return subtractDays(getTodayString(), 1);
711
+ }
712
+ function getTomorrowString() {
713
+ return addDays(getTodayString(), 1);
714
+ }
715
+ function getStartOfDayISO(dateStr) {
716
+ const date = parseLocalDate(dateStr);
717
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).toISOString();
718
+ }
719
+ function getEndOfDayISO(dateStr) {
720
+ const date = parseLocalDate(dateStr);
721
+ return new Date(
722
+ date.getFullYear(),
723
+ date.getMonth(),
724
+ date.getDate(),
725
+ 23,
726
+ 59,
727
+ 59,
728
+ 999
729
+ ).toISOString();
730
+ }
731
+ function getWeekStart(date) {
732
+ const d = new Date(date);
733
+ const day = d.getDay();
734
+ const diff = d.getDate() - day + (day === 0 ? -6 : 1);
735
+ d.setDate(diff);
736
+ d.setHours(0, 0, 0, 0);
737
+ return d;
738
+ }
739
+ function getWeekEnd(weekStart) {
740
+ const d = new Date(weekStart);
741
+ d.setDate(d.getDate() + 6);
742
+ return d;
743
+ }
744
+ function getPreviousWeek(weekStart) {
745
+ const d = new Date(weekStart);
746
+ d.setDate(d.getDate() - 7);
747
+ return d;
748
+ }
749
+ function getNextWeek(weekStart) {
750
+ const d = new Date(weekStart);
751
+ d.setDate(d.getDate() + 7);
752
+ return d;
753
+ }
754
+ function getWeekStartString(baseDate = /* @__PURE__ */ new Date()) {
755
+ return formatDateString(getWeekStart(baseDate));
756
+ }
757
+ function getWeekEndString(baseDate = /* @__PURE__ */ new Date()) {
758
+ return formatDateString(getWeekEnd(getWeekStart(baseDate)));
759
+ }
760
+ function formatWeekRange(weekStart) {
761
+ const weekEnd = getWeekEnd(weekStart);
762
+ const startMonth = weekStart.toLocaleDateString("en-US", { month: "short" });
763
+ const endMonth = weekEnd.toLocaleDateString("en-US", { month: "short" });
764
+ if (startMonth === endMonth) {
765
+ return `${startMonth} ${weekStart.getDate()} - ${weekEnd.getDate()}, ${weekStart.getFullYear()}`;
766
+ }
767
+ return `${startMonth} ${weekStart.getDate()} - ${endMonth} ${weekEnd.getDate()}, ${weekStart.getFullYear()}`;
768
+ }
769
+ function formatFullDate(dateStr) {
770
+ const date = parseLocalDate(dateStr);
771
+ return date.toLocaleDateString("en-US", {
772
+ weekday: "long",
773
+ year: "numeric",
774
+ month: "long",
775
+ day: "numeric"
776
+ });
777
+ }
778
+ function formatDateLong(dateStr) {
779
+ const date = parseLocalDate(dateStr);
780
+ return date.toLocaleDateString("en-GB", {
781
+ weekday: "long",
782
+ day: "numeric",
783
+ month: "long"
784
+ });
785
+ }
786
+ function formatMonthYear(date) {
787
+ return date.toLocaleDateString("en-US", {
788
+ month: "long",
789
+ year: "numeric"
790
+ });
791
+ }
792
+ function formatTimeHHMM(date) {
793
+ const d = typeof date === "string" ? new Date(date) : date;
794
+ return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
795
+ }
796
+ function formatTimeWithSeconds(date) {
797
+ const d = typeof date === "string" ? new Date(date) : date;
798
+ return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
799
+ }
800
+ function formatDateHeader(dateStr) {
801
+ const today = getTodayString();
802
+ if (dateStr < today) {
803
+ return "Overdue";
804
+ }
805
+ if (dateStr === today) {
806
+ return "Today";
807
+ }
808
+ const tomorrowStr = getTomorrowString();
809
+ if (dateStr === tomorrowStr) {
810
+ return "Tomorrow";
811
+ }
812
+ const date = parseLocalDate(dateStr);
813
+ return date.toLocaleDateString("en-US", {
814
+ weekday: "long",
815
+ month: "short",
816
+ day: "numeric"
817
+ });
818
+ }
819
+ function formatDueDateString(dateStr) {
820
+ if (!dateStr) return "No due date";
821
+ const today = getTodayString();
822
+ const tomorrowStr = getTomorrowString();
823
+ if (dateStr < today) {
824
+ const date2 = parseLocalDate(dateStr);
825
+ return `Overdue \u2022 ${date2.toLocaleDateString("en-US", { month: "short", day: "numeric" })}`;
826
+ }
827
+ if (dateStr === today) {
828
+ return "Today";
829
+ }
830
+ if (dateStr === tomorrowStr) {
831
+ return "Tomorrow";
832
+ }
833
+ const date = parseLocalDate(dateStr);
834
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
835
+ const dateYear = date.getFullYear();
836
+ return date.toLocaleDateString("en-US", {
837
+ month: "short",
838
+ day: "numeric",
839
+ year: dateYear !== currentYear ? "numeric" : void 0
840
+ });
841
+ }
842
+ function formatRelativeTimeExtended(dateStr) {
843
+ const date = typeof dateStr === "string" ? new Date(dateStr) : dateStr;
844
+ const now = /* @__PURE__ */ new Date();
845
+ const diffMs = now.getTime() - date.getTime();
846
+ const diffMins = Math.floor(diffMs / 6e4);
847
+ const diffHours = Math.floor(diffMins / 60);
848
+ const diffDays = Math.floor(diffHours / 24);
849
+ if (diffMins < 1) return "Just now";
850
+ if (diffMins < 60) return `${diffMins}m ago`;
851
+ if (diffHours < 24) return `${diffHours}h ago`;
852
+ if (diffDays < 7) return `${diffDays}d ago`;
853
+ return date.toLocaleDateString();
854
+ }
855
+ function formatRelativeDueDate(dateStr, options) {
856
+ const { duePrefix = "Due", overduePrefix = "" } = options || {};
857
+ const date = parseLocalDate(dateStr);
858
+ const now = normalizeToMidnight(/* @__PURE__ */ new Date());
859
+ const diffMs = date.getTime() - now.getTime();
860
+ const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
861
+ if (diffDays < 0) {
862
+ const absDays = Math.abs(diffDays);
863
+ return overduePrefix ? `${overduePrefix} ${absDays} day${absDays !== 1 ? "s" : ""} overdue` : `${absDays} day${absDays !== 1 ? "s" : ""} overdue`;
864
+ }
865
+ if (diffDays === 0) return `${duePrefix} today`;
866
+ if (diffDays === 1) return `${duePrefix} tomorrow`;
867
+ if (diffDays <= 7) return `${duePrefix} in ${diffDays} days`;
868
+ return date.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
869
+ }
870
+ function formatRelativePayDate(dateStr) {
871
+ const date = parseLocalDate(dateStr);
872
+ const now = normalizeToMidnight(/* @__PURE__ */ new Date());
873
+ const diffMs = date.getTime() - now.getTime();
874
+ const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
875
+ if (diffDays < 0) {
876
+ const absDays = Math.abs(diffDays);
877
+ return `${absDays} day${absDays !== 1 ? "s" : ""} ago`;
878
+ }
879
+ if (diffDays === 0) return "Today";
880
+ if (diffDays === 1) return "Tomorrow";
881
+ if (diffDays <= 7) return `In ${diffDays} days`;
882
+ return date.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
883
+ }
884
+ function formatDateLocalized(date, format = "medium") {
885
+ const d = typeof date === "string" ? new Date(date) : date;
886
+ switch (format) {
887
+ case "short":
888
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
889
+ case "medium":
890
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
891
+ case "long":
892
+ return d.toLocaleDateString("en-US", {
893
+ weekday: "long",
894
+ month: "long",
895
+ day: "numeric",
896
+ year: "numeric"
897
+ });
898
+ case "weekday":
899
+ return d.toLocaleDateString("en-US", { weekday: "short" });
900
+ default:
901
+ return d.toLocaleDateString();
902
+ }
903
+ }
904
+ function formatTimeLocalized(date, format = "short") {
905
+ const d = typeof date === "string" ? new Date(date) : date;
906
+ switch (format) {
907
+ case "short":
908
+ return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: true });
909
+ case "withSeconds":
910
+ return d.toLocaleTimeString("en-US", {
911
+ hour: "2-digit",
912
+ minute: "2-digit",
913
+ second: "2-digit",
914
+ hour12: true
915
+ });
916
+ default:
917
+ return d.toLocaleTimeString();
918
+ }
919
+ }
366
920
  // Annotate the CommonJS export names for ESM import in node:
367
921
  0 && (module.exports = {
368
922
  API_URLS,
@@ -381,29 +935,72 @@ function calculateFocusStats(sessions) {
381
935
  LINEAR_PRIORITY_COLORS,
382
936
  LINEAR_PRIORITY_LABELS,
383
937
  SETTING_KEYS,
938
+ activeSessionAtom,
939
+ addDays,
384
940
  calculateFocusStats,
385
941
  calculateLieuBalance,
386
942
  calculateMonthlyExpenses,
387
943
  calculateMonthlyIncome,
388
944
  calculateMonthlySavings,
389
945
  calculateSavingsRate,
946
+ elapsedSecondsAtom,
390
947
  formatCurrency,
391
948
  formatDate,
949
+ formatDateHeader,
950
+ formatDateLocalized,
951
+ formatDateLong,
952
+ formatDateString,
392
953
  formatDueDate,
954
+ formatDueDateString,
955
+ formatFullDate,
956
+ formatMonthYear,
957
+ formatRelativeDueDate,
958
+ formatRelativePayDate,
393
959
  formatRelativeTime,
960
+ formatRelativeTimeExtended,
394
961
  formatTime,
962
+ formatTimeHHMM,
963
+ formatTimeLocalized,
964
+ formatTimeWithSeconds,
395
965
  formatTotalTime,
966
+ formatWeekRange,
967
+ formattedElapsedAtom,
968
+ formattedRemainingAtom,
396
969
  generateId,
397
970
  generateRandomColor,
398
971
  generateShortId,
972
+ getEndOfDayISO,
973
+ getNextWeek,
974
+ getPreviousWeek,
399
975
  getRepoName,
976
+ getStartOfDayISO,
977
+ getTodayMidnight,
978
+ getTodayString,
979
+ getTomorrowString,
980
+ getWeekEnd,
981
+ getWeekEndString,
982
+ getWeekStart,
983
+ getWeekStartString,
984
+ getYesterdayString,
985
+ isDueSoon,
400
986
  isNonEmptyString,
987
+ isOverdue,
401
988
  isPositiveNumber,
989
+ isToday,
402
990
  isValidCurrency,
403
991
  isValidEmail,
404
992
  isValidFrequency,
405
993
  isValidISODate,
406
994
  isValidUrl,
995
+ normalizeToMidnight,
996
+ parseLocalDate,
997
+ progressAtom,
998
+ progressPercentAtom,
999
+ queryKeys,
1000
+ remainingSecondsAtom,
1001
+ subtractDays,
1002
+ targetSecondsAtom,
1003
+ timerStatusAtom,
407
1004
  toMonthlyAmount,
408
1005
  toYearlyAmount,
409
1006
  truncate