@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/README.md +162 -10
- package/dist/atoms/index.d.mts +92 -0
- package/dist/atoms/index.d.ts +92 -0
- package/dist/atoms/index.js +82 -0
- package/dist/atoms/index.js.map +1 -0
- package/dist/atoms/index.mjs +47 -0
- package/dist/atoms/index.mjs.map +1 -0
- package/dist/date/index.d.mts +176 -0
- package/dist/date/index.d.ts +176 -0
- package/dist/date/index.js +347 -0
- package/dist/date/index.js.map +1 -0
- package/dist/date/index.mjs +290 -0
- package/dist/date/index.mjs.map +1 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +600 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +557 -3
- package/dist/index.mjs.map +1 -1
- package/dist/query/index.d.mts +321 -0
- package/dist/query/index.d.ts +321 -0
- package/dist/query/index.js +257 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/index.mjs +232 -0
- package/dist/query/index.mjs.map +1 -0
- package/dist/utils/index.d.mts +2 -1
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +1 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +25 -1
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// src/query/index.ts
|
|
2
|
+
var queryKeys = {
|
|
3
|
+
// -------------------------------------------------------------------------
|
|
4
|
+
// Expenses
|
|
5
|
+
// -------------------------------------------------------------------------
|
|
6
|
+
expenses: {
|
|
7
|
+
all: ["expenses"],
|
|
8
|
+
lists: () => [...queryKeys.expenses.all, "list"],
|
|
9
|
+
list: (filters) => [...queryKeys.expenses.lists(), filters],
|
|
10
|
+
detail: (id) => [...queryKeys.expenses.all, "detail", id]
|
|
11
|
+
},
|
|
12
|
+
// Expense Categories
|
|
13
|
+
expenseCategories: {
|
|
14
|
+
all: ["expenseCategories"],
|
|
15
|
+
list: () => [...queryKeys.expenseCategories.all, "list"]
|
|
16
|
+
},
|
|
17
|
+
// Expense Payments
|
|
18
|
+
expensePayments: {
|
|
19
|
+
all: ["expensePayments"],
|
|
20
|
+
lists: () => [...queryKeys.expensePayments.all, "list"],
|
|
21
|
+
list: (filters) => [...queryKeys.expensePayments.lists(), filters],
|
|
22
|
+
detail: (id) => [...queryKeys.expensePayments.all, "detail", id]
|
|
23
|
+
},
|
|
24
|
+
// -------------------------------------------------------------------------
|
|
25
|
+
// Income
|
|
26
|
+
// -------------------------------------------------------------------------
|
|
27
|
+
incomes: {
|
|
28
|
+
all: ["incomes"],
|
|
29
|
+
lists: () => [...queryKeys.incomes.all, "list"],
|
|
30
|
+
list: (filters) => [...queryKeys.incomes.lists(), filters],
|
|
31
|
+
detail: (id) => [...queryKeys.incomes.all, "detail", id]
|
|
32
|
+
},
|
|
33
|
+
// Income Categories
|
|
34
|
+
incomeCategories: {
|
|
35
|
+
all: ["incomeCategories"],
|
|
36
|
+
list: () => [...queryKeys.incomeCategories.all, "list"]
|
|
37
|
+
},
|
|
38
|
+
// Income Payments
|
|
39
|
+
incomePayments: {
|
|
40
|
+
all: ["incomePayments"],
|
|
41
|
+
lists: () => [...queryKeys.incomePayments.all, "list"],
|
|
42
|
+
list: (filters) => [...queryKeys.incomePayments.lists(), filters],
|
|
43
|
+
detail: (id) => [...queryKeys.incomePayments.all, "detail", id]
|
|
44
|
+
},
|
|
45
|
+
// Exchange Rates
|
|
46
|
+
exchangeRates: {
|
|
47
|
+
all: ["exchangeRates"],
|
|
48
|
+
list: () => [...queryKeys.exchangeRates.all, "list"]
|
|
49
|
+
},
|
|
50
|
+
// -------------------------------------------------------------------------
|
|
51
|
+
// Focus
|
|
52
|
+
// -------------------------------------------------------------------------
|
|
53
|
+
focusSessions: {
|
|
54
|
+
all: ["focusSessions"],
|
|
55
|
+
lists: () => [...queryKeys.focusSessions.all, "list"],
|
|
56
|
+
list: (filters) => [...queryKeys.focusSessions.lists(), filters],
|
|
57
|
+
today: () => [...queryKeys.focusSessions.all, "today"],
|
|
58
|
+
active: () => [...queryKeys.focusSessions.all, "active"],
|
|
59
|
+
detail: (id) => [...queryKeys.focusSessions.all, "detail", id]
|
|
60
|
+
},
|
|
61
|
+
// Focus Categories
|
|
62
|
+
focusCategories: {
|
|
63
|
+
all: ["focusCategories"],
|
|
64
|
+
list: () => [...queryKeys.focusCategories.all, "list"]
|
|
65
|
+
},
|
|
66
|
+
// -------------------------------------------------------------------------
|
|
67
|
+
// Settings
|
|
68
|
+
// -------------------------------------------------------------------------
|
|
69
|
+
settings: {
|
|
70
|
+
all: ["settings"],
|
|
71
|
+
detail: (key) => [...queryKeys.settings.all, key]
|
|
72
|
+
},
|
|
73
|
+
// -------------------------------------------------------------------------
|
|
74
|
+
// EF Work (Lieu Days & Links)
|
|
75
|
+
// -------------------------------------------------------------------------
|
|
76
|
+
lieuDays: {
|
|
77
|
+
all: ["lieuDays"],
|
|
78
|
+
list: () => [...queryKeys.lieuDays.all, "list"],
|
|
79
|
+
balance: () => [...queryKeys.lieuDays.all, "balance"],
|
|
80
|
+
detail: (id) => [...queryKeys.lieuDays.all, "detail", id]
|
|
81
|
+
},
|
|
82
|
+
efLinks: {
|
|
83
|
+
all: ["efLinks"],
|
|
84
|
+
list: () => [...queryKeys.efLinks.all, "list"],
|
|
85
|
+
detail: (id) => [...queryKeys.efLinks.all, "detail", id]
|
|
86
|
+
},
|
|
87
|
+
// -------------------------------------------------------------------------
|
|
88
|
+
// Banking
|
|
89
|
+
// -------------------------------------------------------------------------
|
|
90
|
+
bankConnections: {
|
|
91
|
+
all: ["bankConnections"],
|
|
92
|
+
list: () => [...queryKeys.bankConnections.all, "list"],
|
|
93
|
+
detail: (id) => [...queryKeys.bankConnections.all, "detail", id]
|
|
94
|
+
},
|
|
95
|
+
bankTransactions: {
|
|
96
|
+
all: ["bankTransactions"],
|
|
97
|
+
lists: () => [...queryKeys.bankTransactions.all, "list"],
|
|
98
|
+
list: (filters) => [...queryKeys.bankTransactions.lists(), filters],
|
|
99
|
+
stats: (connectionId) => [...queryKeys.bankTransactions.all, "stats", connectionId]
|
|
100
|
+
},
|
|
101
|
+
// -------------------------------------------------------------------------
|
|
102
|
+
// Health
|
|
103
|
+
// -------------------------------------------------------------------------
|
|
104
|
+
healthCategories: {
|
|
105
|
+
all: ["healthCategories"],
|
|
106
|
+
list: () => [...queryKeys.healthCategories.all, "list"],
|
|
107
|
+
detail: (id) => [...queryKeys.healthCategories.all, "detail", id]
|
|
108
|
+
},
|
|
109
|
+
healthHabits: {
|
|
110
|
+
all: ["healthHabits"],
|
|
111
|
+
lists: () => [...queryKeys.healthHabits.all, "list"],
|
|
112
|
+
list: (filters) => [...queryKeys.healthHabits.lists(), filters],
|
|
113
|
+
detail: (id) => [...queryKeys.healthHabits.all, "detail", id]
|
|
114
|
+
},
|
|
115
|
+
healthCompletions: {
|
|
116
|
+
all: ["healthCompletions"],
|
|
117
|
+
lists: () => [...queryKeys.healthCompletions.all, "list"],
|
|
118
|
+
list: (filters) => [...queryKeys.healthCompletions.lists(), filters],
|
|
119
|
+
forHabit: (habitId) => [...queryKeys.healthCompletions.all, "habit", habitId],
|
|
120
|
+
forDate: (date) => [...queryKeys.healthCompletions.all, "date", date]
|
|
121
|
+
},
|
|
122
|
+
healthStats: {
|
|
123
|
+
all: ["healthStats"],
|
|
124
|
+
forDate: (date) => [...queryKeys.healthStats.all, date ?? "today"]
|
|
125
|
+
},
|
|
126
|
+
// -------------------------------------------------------------------------
|
|
127
|
+
// Prompts
|
|
128
|
+
// -------------------------------------------------------------------------
|
|
129
|
+
promptCategories: {
|
|
130
|
+
all: ["promptCategories"],
|
|
131
|
+
list: () => [...queryKeys.promptCategories.all, "list"],
|
|
132
|
+
detail: (id) => [...queryKeys.promptCategories.all, "detail", id]
|
|
133
|
+
},
|
|
134
|
+
promptLabels: {
|
|
135
|
+
all: ["promptLabels"],
|
|
136
|
+
list: () => [...queryKeys.promptLabels.all, "list"],
|
|
137
|
+
detail: (id) => [...queryKeys.promptLabels.all, "detail", id]
|
|
138
|
+
},
|
|
139
|
+
prompts: {
|
|
140
|
+
all: ["prompts"],
|
|
141
|
+
lists: () => [...queryKeys.prompts.all, "list"],
|
|
142
|
+
list: (filters) => [...queryKeys.prompts.lists(), filters],
|
|
143
|
+
detail: (id) => [...queryKeys.prompts.all, "detail", id]
|
|
144
|
+
},
|
|
145
|
+
// -------------------------------------------------------------------------
|
|
146
|
+
// Training
|
|
147
|
+
// -------------------------------------------------------------------------
|
|
148
|
+
trainingActivities: {
|
|
149
|
+
all: ["trainingActivities"],
|
|
150
|
+
list: () => [...queryKeys.trainingActivities.all, "list"],
|
|
151
|
+
detail: (id) => [...queryKeys.trainingActivities.all, "detail", id],
|
|
152
|
+
byStravaType: (stravaType) => [...queryKeys.trainingActivities.all, "strava", stravaType]
|
|
153
|
+
},
|
|
154
|
+
trainingSessions: {
|
|
155
|
+
all: ["trainingSessions"],
|
|
156
|
+
lists: () => [...queryKeys.trainingSessions.all, "list"],
|
|
157
|
+
list: (filters) => [...queryKeys.trainingSessions.lists(), filters],
|
|
158
|
+
detail: (id) => [...queryKeys.trainingSessions.all, "detail", id],
|
|
159
|
+
byStravaId: (stravaActivityId) => [...queryKeys.trainingSessions.all, "strava", stravaActivityId]
|
|
160
|
+
},
|
|
161
|
+
plannedSessions: {
|
|
162
|
+
all: ["plannedSessions"],
|
|
163
|
+
lists: () => [...queryKeys.plannedSessions.all, "list"],
|
|
164
|
+
list: (filters) => [...queryKeys.plannedSessions.lists(), filters],
|
|
165
|
+
detail: (id) => [...queryKeys.plannedSessions.all, "detail", id],
|
|
166
|
+
upcoming: () => [...queryKeys.plannedSessions.all, "upcoming"]
|
|
167
|
+
},
|
|
168
|
+
races: {
|
|
169
|
+
all: ["races"],
|
|
170
|
+
lists: () => [...queryKeys.races.all, "list"],
|
|
171
|
+
list: (filters) => [...queryKeys.races.lists(), filters],
|
|
172
|
+
detail: (id) => [...queryKeys.races.all, "detail", id],
|
|
173
|
+
next: () => [...queryKeys.races.all, "next"]
|
|
174
|
+
},
|
|
175
|
+
trainingStats: {
|
|
176
|
+
all: ["trainingStats"],
|
|
177
|
+
forRange: (dateFrom, dateTo) => [...queryKeys.trainingStats.all, dateFrom, dateTo]
|
|
178
|
+
},
|
|
179
|
+
// Strava
|
|
180
|
+
strava: {
|
|
181
|
+
all: ["strava"],
|
|
182
|
+
athlete: () => [...queryKeys.strava.all, "athlete"],
|
|
183
|
+
connected: () => [...queryKeys.strava.all, "connected"],
|
|
184
|
+
activities: (options) => [...queryKeys.strava.all, "activities", options]
|
|
185
|
+
},
|
|
186
|
+
// -------------------------------------------------------------------------
|
|
187
|
+
// Journal
|
|
188
|
+
// -------------------------------------------------------------------------
|
|
189
|
+
journalEntries: {
|
|
190
|
+
all: ["journalEntries"],
|
|
191
|
+
lists: () => [...queryKeys.journalEntries.all, "list"],
|
|
192
|
+
list: (filters) => [...queryKeys.journalEntries.lists(), filters],
|
|
193
|
+
forDate: (date) => [...queryKeys.journalEntries.all, "date", date]
|
|
194
|
+
},
|
|
195
|
+
// Journal data (aggregated)
|
|
196
|
+
journalData: {
|
|
197
|
+
all: ["journalData"],
|
|
198
|
+
forDate: (date) => [...queryKeys.journalData.all, date]
|
|
199
|
+
},
|
|
200
|
+
// -------------------------------------------------------------------------
|
|
201
|
+
// Chat
|
|
202
|
+
// -------------------------------------------------------------------------
|
|
203
|
+
chatConversations: {
|
|
204
|
+
all: ["chatConversations"],
|
|
205
|
+
list: () => [...queryKeys.chatConversations.all, "list"],
|
|
206
|
+
detail: (id) => [...queryKeys.chatConversations.all, "detail", id]
|
|
207
|
+
},
|
|
208
|
+
chatMessages: {
|
|
209
|
+
all: ["chatMessages"],
|
|
210
|
+
forConversation: (conversationId) => [...queryKeys.chatMessages.all, "conversation", conversationId]
|
|
211
|
+
},
|
|
212
|
+
// -------------------------------------------------------------------------
|
|
213
|
+
// Google Calendar
|
|
214
|
+
// -------------------------------------------------------------------------
|
|
215
|
+
googleCalendar: {
|
|
216
|
+
all: ["googleCalendar"],
|
|
217
|
+
calendars: () => [...queryKeys.googleCalendar.all, "calendars"],
|
|
218
|
+
events: (calendarId, timeMin, timeMax) => [...queryKeys.googleCalendar.all, "events", calendarId, timeMin, timeMax]
|
|
219
|
+
},
|
|
220
|
+
// -------------------------------------------------------------------------
|
|
221
|
+
// Linear
|
|
222
|
+
// -------------------------------------------------------------------------
|
|
223
|
+
linear: {
|
|
224
|
+
all: ["linear"],
|
|
225
|
+
issues: (filters) => [...queryKeys.linear.all, "issues", filters],
|
|
226
|
+
projects: () => [...queryKeys.linear.all, "projects"]
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
export {
|
|
230
|
+
queryKeys
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/query/index.ts"],"sourcesContent":["/**\n * Query Keys Factory\n *\n * Centralized query key management for TanStack Query.\n * Used by both desktop and mobile apps for consistent cache invalidation.\n *\n * @example\n * import { queryKeys } from '@mrck-labs/vanaheim-shared/query'\n *\n * // In a query hook\n * useQuery({\n * queryKey: queryKeys.focusSessions.today(),\n * queryFn: ...\n * })\n *\n * // Invalidating\n * queryClient.invalidateQueries({ queryKey: queryKeys.focusSessions.all })\n */\n\n// ============================================================================\n// Query Keys Factory\n// ============================================================================\n\nexport const queryKeys = {\n // -------------------------------------------------------------------------\n // Expenses\n // -------------------------------------------------------------------------\n expenses: {\n all: ['expenses'] as const,\n lists: () => [...queryKeys.expenses.all, 'list'] as const,\n list: (filters?: { isActive?: boolean; categoryId?: string }) =>\n [...queryKeys.expenses.lists(), filters] as const,\n detail: (id: string) => [...queryKeys.expenses.all, 'detail', id] as const,\n },\n\n // Expense Categories\n expenseCategories: {\n all: ['expenseCategories'] as const,\n list: () => [...queryKeys.expenseCategories.all, 'list'] as const,\n },\n\n // Expense Payments\n expensePayments: {\n all: ['expensePayments'] as const,\n lists: () => [...queryKeys.expensePayments.all, 'list'] as const,\n list: (filters?: { expenseId?: string; dateFrom?: string; dateTo?: string }) =>\n [...queryKeys.expensePayments.lists(), filters] as const,\n detail: (id: string) => [...queryKeys.expensePayments.all, 'detail', id] as const,\n },\n\n // -------------------------------------------------------------------------\n // Income\n // -------------------------------------------------------------------------\n incomes: {\n all: ['incomes'] as const,\n lists: () => [...queryKeys.incomes.all, 'list'] as const,\n list: (filters?: { isActive?: boolean; categoryId?: string }) =>\n [...queryKeys.incomes.lists(), filters] as const,\n detail: (id: string) => [...queryKeys.incomes.all, 'detail', id] as const,\n },\n\n // Income Categories\n incomeCategories: {\n all: ['incomeCategories'] as const,\n list: () => [...queryKeys.incomeCategories.all, 'list'] as const,\n },\n\n // Income Payments\n incomePayments: {\n all: ['incomePayments'] as const,\n lists: () => [...queryKeys.incomePayments.all, 'list'] as const,\n list: (filters?: { incomeId?: string; dateFrom?: string; dateTo?: string }) =>\n [...queryKeys.incomePayments.lists(), filters] as const,\n detail: (id: string) => [...queryKeys.incomePayments.all, 'detail', id] as const,\n },\n\n // Exchange Rates\n exchangeRates: {\n all: ['exchangeRates'] as const,\n list: () => [...queryKeys.exchangeRates.all, 'list'] as const,\n },\n\n // -------------------------------------------------------------------------\n // Focus\n // -------------------------------------------------------------------------\n focusSessions: {\n all: ['focusSessions'] as const,\n lists: () => [...queryKeys.focusSessions.all, 'list'] as const,\n list: (filters?: {\n date?: string\n categoryId?: string\n status?: string\n startedAfter?: string\n startedBefore?: string\n }) => [...queryKeys.focusSessions.lists(), filters] as const,\n today: () => [...queryKeys.focusSessions.all, 'today'] as const,\n active: () => [...queryKeys.focusSessions.all, 'active'] as const,\n detail: (id: string) => [...queryKeys.focusSessions.all, 'detail', id] as const,\n },\n\n // Focus Categories\n focusCategories: {\n all: ['focusCategories'] as const,\n list: () => [...queryKeys.focusCategories.all, 'list'] as const,\n },\n\n // -------------------------------------------------------------------------\n // Settings\n // -------------------------------------------------------------------------\n settings: {\n all: ['settings'] as const,\n detail: (key: string) => [...queryKeys.settings.all, key] as const,\n },\n\n // -------------------------------------------------------------------------\n // EF Work (Lieu Days & Links)\n // -------------------------------------------------------------------------\n lieuDays: {\n all: ['lieuDays'] as const,\n list: () => [...queryKeys.lieuDays.all, 'list'] as const,\n balance: () => [...queryKeys.lieuDays.all, 'balance'] as const,\n detail: (id: string) => [...queryKeys.lieuDays.all, 'detail', id] as const,\n },\n\n efLinks: {\n all: ['efLinks'] as const,\n list: () => [...queryKeys.efLinks.all, 'list'] as const,\n detail: (id: string) => [...queryKeys.efLinks.all, 'detail', id] as const,\n },\n\n // -------------------------------------------------------------------------\n // Banking\n // -------------------------------------------------------------------------\n bankConnections: {\n all: ['bankConnections'] as const,\n list: () => [...queryKeys.bankConnections.all, 'list'] as const,\n detail: (id: string) => [...queryKeys.bankConnections.all, 'detail', id] as const,\n },\n\n bankTransactions: {\n all: ['bankTransactions'] as const,\n lists: () => [...queryKeys.bankTransactions.all, 'list'] as const,\n list: (filters?: { connectionId?: string; dateFrom?: string; dateTo?: string }) =>\n [...queryKeys.bankTransactions.lists(), filters] as const,\n stats: (connectionId?: string) =>\n [...queryKeys.bankTransactions.all, 'stats', connectionId] as const,\n },\n\n // -------------------------------------------------------------------------\n // Health\n // -------------------------------------------------------------------------\n healthCategories: {\n all: ['healthCategories'] as const,\n list: () => [...queryKeys.healthCategories.all, 'list'] as const,\n detail: (id: string) => [...queryKeys.healthCategories.all, 'detail', id] as const,\n },\n\n healthHabits: {\n all: ['healthHabits'] as const,\n lists: () => [...queryKeys.healthHabits.all, 'list'] as const,\n list: (filters?: { categoryId?: string; isActive?: boolean }) =>\n [...queryKeys.healthHabits.lists(), filters] as const,\n detail: (id: string) => [...queryKeys.healthHabits.all, 'detail', id] as const,\n },\n\n healthCompletions: {\n all: ['healthCompletions'] as const,\n lists: () => [...queryKeys.healthCompletions.all, 'list'] as const,\n list: (filters?: { habitId?: string; dateFrom?: string; dateTo?: string }) =>\n [...queryKeys.healthCompletions.lists(), filters] as const,\n forHabit: (habitId: string) => [...queryKeys.healthCompletions.all, 'habit', habitId] as const,\n forDate: (date: string) => [...queryKeys.healthCompletions.all, 'date', date] as const,\n },\n\n healthStats: {\n all: ['healthStats'] as const,\n forDate: (date?: string) => [...queryKeys.healthStats.all, date ?? 'today'] as const,\n },\n\n // -------------------------------------------------------------------------\n // Prompts\n // -------------------------------------------------------------------------\n promptCategories: {\n all: ['promptCategories'] as const,\n list: () => [...queryKeys.promptCategories.all, 'list'] as const,\n detail: (id: string) => [...queryKeys.promptCategories.all, 'detail', id] as const,\n },\n\n promptLabels: {\n all: ['promptLabels'] as const,\n list: () => [...queryKeys.promptLabels.all, 'list'] as const,\n detail: (id: string) => [...queryKeys.promptLabels.all, 'detail', id] as const,\n },\n\n prompts: {\n all: ['prompts'] as const,\n lists: () => [...queryKeys.prompts.all, 'list'] as const,\n list: (filters?: {\n categoryId?: string\n labelId?: string\n isFavorite?: boolean\n search?: string\n }) => [...queryKeys.prompts.lists(), filters] as const,\n detail: (id: string) => [...queryKeys.prompts.all, 'detail', id] as const,\n },\n\n // -------------------------------------------------------------------------\n // Training\n // -------------------------------------------------------------------------\n trainingActivities: {\n all: ['trainingActivities'] as const,\n list: () => [...queryKeys.trainingActivities.all, 'list'] as const,\n detail: (id: string) => [...queryKeys.trainingActivities.all, 'detail', id] as const,\n byStravaType: (stravaType: string) =>\n [...queryKeys.trainingActivities.all, 'strava', stravaType] as const,\n },\n\n trainingSessions: {\n all: ['trainingSessions'] as const,\n lists: () => [...queryKeys.trainingSessions.all, 'list'] as const,\n list: (filters?: {\n activityId?: string\n dateFrom?: string\n dateTo?: string\n source?: 'manual' | 'strava'\n }) => [...queryKeys.trainingSessions.lists(), filters] as const,\n detail: (id: string) => [...queryKeys.trainingSessions.all, 'detail', id] as const,\n byStravaId: (stravaActivityId: string) =>\n [...queryKeys.trainingSessions.all, 'strava', stravaActivityId] as const,\n },\n\n plannedSessions: {\n all: ['plannedSessions'] as const,\n lists: () => [...queryKeys.plannedSessions.all, 'list'] as const,\n list: (filters?: {\n activityId?: string\n dateFrom?: string\n dateTo?: string\n isCompleted?: boolean\n }) => [...queryKeys.plannedSessions.lists(), filters] as const,\n detail: (id: string) => [...queryKeys.plannedSessions.all, 'detail', id] as const,\n upcoming: () => [...queryKeys.plannedSessions.all, 'upcoming'] as const,\n },\n\n races: {\n all: ['races'] as const,\n lists: () => [...queryKeys.races.all, 'list'] as const,\n list: (filters?: {\n type?: 'race' | 'event' | 'goal'\n status?: 'upcoming' | 'completed' | 'dns' | 'dnf'\n dateFrom?: string\n dateTo?: string\n }) => [...queryKeys.races.lists(), filters] as const,\n detail: (id: string) => [...queryKeys.races.all, 'detail', id] as const,\n next: () => [...queryKeys.races.all, 'next'] as const,\n },\n\n trainingStats: {\n all: ['trainingStats'] as const,\n forRange: (dateFrom?: string, dateTo?: string) =>\n [...queryKeys.trainingStats.all, dateFrom, dateTo] as const,\n },\n\n // Strava\n strava: {\n all: ['strava'] as const,\n athlete: () => [...queryKeys.strava.all, 'athlete'] as const,\n connected: () => [...queryKeys.strava.all, 'connected'] as const,\n activities: (options?: { after?: number; before?: number }) =>\n [...queryKeys.strava.all, 'activities', options] as const,\n },\n\n // -------------------------------------------------------------------------\n // Journal\n // -------------------------------------------------------------------------\n journalEntries: {\n all: ['journalEntries'] as const,\n lists: () => [...queryKeys.journalEntries.all, 'list'] as const,\n list: (filters?: { dateFrom?: string; dateTo?: string }) =>\n [...queryKeys.journalEntries.lists(), filters] as const,\n forDate: (date: string) => [...queryKeys.journalEntries.all, 'date', date] as const,\n },\n\n // Journal data (aggregated)\n journalData: {\n all: ['journalData'] as const,\n forDate: (date: string) => [...queryKeys.journalData.all, date] as const,\n },\n\n // -------------------------------------------------------------------------\n // Chat\n // -------------------------------------------------------------------------\n chatConversations: {\n all: ['chatConversations'] as const,\n list: () => [...queryKeys.chatConversations.all, 'list'] as const,\n detail: (id: string) => [...queryKeys.chatConversations.all, 'detail', id] as const,\n },\n\n chatMessages: {\n all: ['chatMessages'] as const,\n forConversation: (conversationId: string) =>\n [...queryKeys.chatMessages.all, 'conversation', conversationId] as const,\n },\n\n // -------------------------------------------------------------------------\n // Google Calendar\n // -------------------------------------------------------------------------\n googleCalendar: {\n all: ['googleCalendar'] as const,\n calendars: () => [...queryKeys.googleCalendar.all, 'calendars'] as const,\n events: (calendarId: string, timeMin?: string, timeMax?: string) =>\n [...queryKeys.googleCalendar.all, 'events', calendarId, timeMin, timeMax] as const,\n },\n\n // -------------------------------------------------------------------------\n // Linear\n // -------------------------------------------------------------------------\n linear: {\n all: ['linear'] as const,\n issues: (filters?: { projectId?: string; status?: string }) =>\n [...queryKeys.linear.all, 'issues', filters] as const,\n projects: () => [...queryKeys.linear.all, 'projects'] as const,\n },\n} as const\n\n// ============================================================================\n// Type Exports\n// ============================================================================\n\nexport type QueryKeys = typeof queryKeys\n\n"],"mappings":"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","names":[]}
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -19,7 +19,8 @@ declare function formatTime(seconds: number): string;
|
|
|
19
19
|
declare function formatTotalTime(seconds: number): string;
|
|
20
20
|
/**
|
|
21
21
|
* Format a number as currency
|
|
22
|
-
*
|
|
22
|
+
* Uses Swiss German locale for authentic Swiss formatting (apostrophe as thousands separator)
|
|
23
|
+
* @example formatCurrency(1234.56, 'CHF') => "CHF 1'234.56"
|
|
23
24
|
*/
|
|
24
25
|
declare function formatCurrency(amount: number, currency: string, locale?: string): string;
|
|
25
26
|
/**
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -19,7 +19,8 @@ declare function formatTime(seconds: number): string;
|
|
|
19
19
|
declare function formatTotalTime(seconds: number): string;
|
|
20
20
|
/**
|
|
21
21
|
* Format a number as currency
|
|
22
|
-
*
|
|
22
|
+
* Uses Swiss German locale for authentic Swiss formatting (apostrophe as thousands separator)
|
|
23
|
+
* @example formatCurrency(1234.56, 'CHF') => "CHF 1'234.56"
|
|
23
24
|
*/
|
|
24
25
|
declare function formatCurrency(amount: number, currency: string, locale?: string): string;
|
|
25
26
|
/**
|
package/dist/utils/index.js
CHANGED
|
@@ -63,7 +63,7 @@ function formatTotalTime(seconds) {
|
|
|
63
63
|
}
|
|
64
64
|
return `${mins}m`;
|
|
65
65
|
}
|
|
66
|
-
function formatCurrency(amount, currency, locale = "
|
|
66
|
+
function formatCurrency(amount, currency, locale = "de-CH") {
|
|
67
67
|
return new Intl.NumberFormat(locale, {
|
|
68
68
|
style: "currency",
|
|
69
69
|
currency,
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/index.ts","../../src/utils/formatters.ts","../../src/utils/generators.ts","../../src/utils/validators.ts","../../src/constants/index.ts","../../src/utils/calculations.ts"],"sourcesContent":["/**\n * Utils Index\n *\n * Re-exports all utility functions.\n */\n\nexport * from \"./formatters\";\nexport * from \"./generators\";\nexport * from \"./validators\";\nexport * from \"./calculations\";\n","/**\n * Formatting Utilities\n *\n * Pure functions for formatting data.\n */\n\n/**\n * Format seconds into MM:SS format\n * @example formatTime(125) => \"02:05\"\n */\nexport function formatTime(seconds: number): string {\n const mins = Math.floor(seconds / 60)\n const secs = seconds % 60\n return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`\n}\n\n/**\n * Format seconds into human-readable total time\n * @example formatTotalTime(3700) => \"1h 1m\"\n * @example formatTotalTime(1800) => \"30m\"\n */\nexport function formatTotalTime(seconds: number): string {\n const hours = Math.floor(seconds / 3600)\n const mins = Math.floor((seconds % 3600) / 60)\n if (hours > 0) {\n return `${hours}h ${mins}m`\n }\n return `${mins}m`\n}\n\n/**\n * Format a number as currency\n * @example formatCurrency(1234.56, 'CHF') => \"CHF 1,234.56\"\n */\nexport function formatCurrency(\n amount: number,\n currency: string,\n locale: string = 'en-CH'\n): string {\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(amount)\n}\n\n/**\n * Format a date as relative time (e.g., \"2h ago\", \"3d ago\")\n */\nexport function formatRelativeTime(dateStr: string): string {\n const date = new Date(dateStr)\n const now = new Date()\n const diffMs = now.getTime() - date.getTime()\n const diffMins = Math.floor(diffMs / 60000)\n const diffHours = Math.floor(diffMins / 60)\n const diffDays = Math.floor(diffHours / 24)\n\n if (diffMins < 1) return 'Just now'\n if (diffMins < 60) return `${diffMins}m ago`\n if (diffHours < 24) return `${diffHours}h ago`\n if (diffDays < 7) return `${diffDays}d ago`\n return date.toLocaleDateString()\n}\n\n/**\n * Format a date string to a readable format\n * @example formatDate('2024-01-15') => \"Jan 15, 2024\"\n */\nexport function formatDate(\n dateStr: string,\n options: Intl.DateTimeFormatOptions = {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n }\n): string {\n return new Date(dateStr).toLocaleDateString('en-US', options)\n}\n\n/**\n * Format a due date with context (Today, Tomorrow, Overdue, etc.)\n */\nexport function formatDueDate(dueDate: string | null): {\n text: string\n isOverdue: boolean\n} {\n if (!dueDate) return { text: '', isOverdue: false }\n\n const due = new Date(dueDate)\n const today = new Date()\n today.setHours(0, 0, 0, 0)\n\n const tomorrow = new Date(today)\n tomorrow.setDate(tomorrow.getDate() + 1)\n\n const dueDay = new Date(due)\n dueDay.setHours(0, 0, 0, 0)\n\n const isOverdue = dueDay < today\n\n if (dueDay.getTime() === today.getTime()) {\n return { text: 'Today', isOverdue: false }\n } else if (dueDay.getTime() === tomorrow.getTime()) {\n return { text: 'Tomorrow', isOverdue: false }\n } else if (isOverdue) {\n const daysAgo = Math.ceil(\n (today.getTime() - dueDay.getTime()) / (1000 * 60 * 60 * 24)\n )\n return { text: `${daysAgo}d overdue`, isOverdue: true }\n } else {\n return {\n text: due.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),\n isOverdue: false,\n }\n }\n}\n\n/**\n * Truncate a string with ellipsis\n * @example truncate(\"Hello World\", 5) => \"Hello...\"\n */\nexport function truncate(str: string, maxLength: number): string {\n if (str.length <= maxLength) return str\n return str.slice(0, maxLength) + '...'\n}\n\n/**\n * Extract repo name from GitHub URL\n * @example getRepoName(\"https://github.com/owner/repo\") => \"owner/repo\"\n */\nexport function getRepoName(url: string): string {\n const match = url.match(/github\\.com\\/(.+)$/)\n return match ? match[1] : url\n}\n\n","/**\n * ID and Data Generators\n *\n * Pure functions for generating IDs and data.\n */\n\n/**\n * Generate a unique ID\n * Uses crypto.randomUUID if available, falls back to timestamp-based ID\n *\n * Note: This works in both Node.js and browser environments.\n * React Native needs the fallback since crypto.randomUUID isn't available.\n */\nexport function generateId(): string {\n // Check if crypto.randomUUID is available (Node.js 19+, modern browsers)\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback: UUID v4-like implementation\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/**\n * Generate a short ID (timestamp + random)\n * Format: {timestamp}-{random7chars}\n * @example \"1732547123456-k8f3j2m\"\n */\nexport function generateShortId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n/**\n * Generate a random color in hex format\n */\nexport function generateRandomColor(): string {\n const colors = [\n '#ef4444', // red\n '#f97316', // orange\n '#eab308', // yellow\n '#22c55e', // green\n '#14b8a6', // teal\n '#3b82f6', // blue\n '#8b5cf6', // violet\n '#ec4899', // pink\n ]\n return colors[Math.floor(Math.random() * colors.length)]\n}\n\n","/**\n * Validation Utilities\n *\n * Pure functions for validating data.\n */\n\n/**\n * Check if a string is a valid email\n */\nexport function isValidEmail(email: string): boolean {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(email)\n}\n\n/**\n * Check if a string is a valid URL\n */\nexport function isValidUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Check if a string is a valid ISO date (YYYY-MM-DD)\n */\nexport function isValidISODate(dateStr: string): boolean {\n const regex = /^\\d{4}-\\d{2}-\\d{2}$/\n if (!regex.test(dateStr)) return false\n\n const date = new Date(dateStr)\n return !isNaN(date.getTime())\n}\n\n/**\n * Check if a value is a valid currency code\n */\nexport function isValidCurrency(currency: string): boolean {\n const validCurrencies = ['CHF', 'USD', 'EUR', 'PLN']\n return validCurrencies.includes(currency)\n}\n\n/**\n * Check if a value is a valid frequency\n */\nexport function isValidFrequency(frequency: string): boolean {\n const validFrequencies = ['monthly', 'yearly', '6-monthly', 'weekly', 'one-time']\n return validFrequencies.includes(frequency)\n}\n\n/**\n * Validate a positive number\n */\nexport function isPositiveNumber(value: unknown): value is number {\n return typeof value === 'number' && !isNaN(value) && value > 0\n}\n\n/**\n * Validate a non-empty string\n */\nexport function isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0\n}\n\n","/**\n * Shared Constants\n *\n * Common constants used across Vanaheim apps.\n */\n\n// ============================================================================\n// Currency\n// ============================================================================\n\nexport const CURRENCIES = ['CHF', 'USD', 'EUR', 'PLN'] as const\nexport type Currency = (typeof CURRENCIES)[number]\n\nexport const CURRENCY_SYMBOLS: Record<Currency, string> = {\n CHF: 'CHF',\n USD: '$',\n EUR: '€',\n PLN: 'zł',\n}\n\nexport const CURRENCY_NAMES: Record<Currency, string> = {\n CHF: 'Swiss Franc',\n USD: 'US Dollar',\n EUR: 'Euro',\n PLN: 'Polish Złoty',\n}\n\n// ============================================================================\n// Frequency\n// ============================================================================\n\nexport const FREQUENCIES = [\n 'monthly',\n 'yearly',\n '6-monthly',\n 'weekly',\n 'one-time',\n] as const\nexport type Frequency = (typeof FREQUENCIES)[number]\n\nexport const FREQUENCY_LABELS: Record<Frequency, string> = {\n monthly: 'Monthly',\n yearly: 'Yearly',\n '6-monthly': 'Every 6 Months',\n weekly: 'Weekly',\n 'one-time': 'One Time',\n}\n\nexport const FREQUENCY_MULTIPLIERS: Record<Frequency, number> = {\n monthly: 12,\n yearly: 1,\n '6-monthly': 2,\n weekly: 52,\n 'one-time': 1,\n}\n\n// ============================================================================\n// Focus\n// ============================================================================\n\nexport const DEFAULT_FOCUS_DURATIONS = [15, 25, 30, 45, 60, 90] as const\nexport type FocusDuration = (typeof DEFAULT_FOCUS_DURATIONS)[number]\n\nexport const FOCUS_STATUS = ['active', 'completed', 'abandoned'] as const\nexport type FocusStatus = (typeof FOCUS_STATUS)[number]\n\n// ============================================================================\n// Linear\n// ============================================================================\n\nexport const LINEAR_PRIORITIES = [0, 1, 2, 3, 4] as const\nexport type LinearPriorityValue = (typeof LINEAR_PRIORITIES)[number]\n\nexport const LINEAR_PRIORITY_COLORS: Record<LinearPriorityValue, string> = {\n 0: '#6b7280', // No priority - gray\n 1: '#ef4444', // Urgent - red\n 2: '#f97316', // High - orange\n 3: '#eab308', // Medium - yellow\n 4: '#3b82f6', // Low - blue\n}\n\n// ============================================================================\n// Cloud Agents\n// ============================================================================\n\nexport const CLOUD_AGENT_STATUSES = [\n 'CREATING',\n 'RUNNING',\n 'FINISHED',\n 'FAILED',\n 'CANCELLED',\n] as const\n\nexport const CLOUD_AGENT_STATUS_EMOJI: Record<string, string> = {\n CREATING: '🔨',\n RUNNING: '⏳',\n FINISHED: '✅',\n FAILED: '❌',\n CANCELLED: '🚫',\n}\n\nexport const CLOUD_AGENT_STATUS_COLORS: Record<string, string> = {\n CREATING: '#3b82f6',\n RUNNING: '#3b82f6',\n FINISHED: '#10b981',\n FAILED: '#ef4444',\n CANCELLED: '#6b7280',\n}\n\n// ============================================================================\n// API URLs\n// ============================================================================\n\nexport const API_URLS = {\n CURSOR_CLOUD: 'https://api.cursor.com',\n LINEAR_GRAPHQL: 'https://api.linear.app/graphql',\n} as const\n\n// ============================================================================\n// Settings Keys\n// ============================================================================\n\nexport const SETTING_KEYS = {\n // AI\n AI_MODEL: 'ai_model',\n AI_REASONING_ENABLED: 'ai_reasoning_enabled',\n\n // API Keys\n OPENAI_API_KEY: 'openai_api_key',\n ANTHROPIC_API_KEY: 'anthropic_api_key',\n CURSOR_API_KEY: 'cursor_api_key',\n LINEAR_API_KEY: 'linear_api_key',\n\n // Google\n GOOGLE_CLIENT_ID: 'google_client_id',\n GOOGLE_CLIENT_SECRET: 'google_client_secret',\n GOOGLE_CALENDAR_TOKENS: 'google_calendar_tokens',\n SELECTED_CALENDAR_IDS: 'selected_calendar_ids',\n} as const\n\nexport type SettingKey = (typeof SETTING_KEYS)[keyof typeof SETTING_KEYS]\n\n","/**\n * Calculation Utilities\n *\n * Pure functions for business logic calculations.\n */\n\nimport type { Expense, Income } from '../types/database'\nimport { FREQUENCY_MULTIPLIERS, type Frequency } from '../constants'\n\n/**\n * Convert an amount to yearly based on frequency\n */\nexport function toYearlyAmount(amount: number, frequency: Frequency): number {\n const multiplier = FREQUENCY_MULTIPLIERS[frequency] || 1\n return amount * multiplier\n}\n\n/**\n * Convert an amount to monthly based on frequency\n */\nexport function toMonthlyAmount(amount: number, frequency: Frequency): number {\n const yearly = toYearlyAmount(amount, frequency)\n return yearly / 12\n}\n\n/**\n * Calculate total expenses (monthly)\n */\nexport function calculateMonthlyExpenses(expenses: Expense[]): number {\n return expenses\n .filter((e) => e.isActive)\n .reduce((total, expense) => {\n const monthly = toMonthlyAmount(\n expense.amount,\n expense.frequency as Frequency\n )\n // Apply share percentage\n const myShare = (monthly * expense.sharePercentage) / 100\n return total + myShare\n }, 0)\n}\n\n/**\n * Calculate total income (monthly)\n */\nexport function calculateMonthlyIncome(incomes: Income[]): number {\n return incomes\n .filter((i) => i.isActive)\n .reduce((total, income) => {\n const monthly = toMonthlyAmount(income.amount, income.frequency as Frequency)\n return total + monthly\n }, 0)\n}\n\n/**\n * Calculate savings (income - expenses)\n */\nexport function calculateMonthlySavings(\n incomes: Income[],\n expenses: Expense[]\n): number {\n return calculateMonthlyIncome(incomes) - calculateMonthlyExpenses(expenses)\n}\n\n/**\n * Calculate savings rate as percentage\n */\nexport function calculateSavingsRate(\n incomes: Income[],\n expenses: Expense[]\n): number {\n const income = calculateMonthlyIncome(incomes)\n if (income === 0) return 0\n\n const savings = calculateMonthlySavings(incomes, expenses)\n return (savings / income) * 100\n}\n\n/**\n * Calculate lieu day balance\n */\nexport function calculateLieuBalance(\n lieuDays: Array<{ type: 'earned' | 'used' }>\n): number {\n return lieuDays.reduce((balance, day) => {\n return day.type === 'earned' ? balance + 1 : balance - 1\n }, 0)\n}\n\n/**\n * Calculate focus time stats for a period\n */\nexport function calculateFocusStats(\n sessions: Array<{ actualSeconds: number; status: string }>\n): {\n totalSeconds: number\n completedCount: number\n abandonedCount: number\n completionRate: number\n} {\n const completed = sessions.filter((s) => s.status === 'completed')\n const abandoned = sessions.filter((s) => s.status === 'abandoned')\n\n const totalSeconds = completed.reduce((sum, s) => sum + s.actualSeconds, 0)\n const completionRate =\n sessions.length > 0 ? (completed.length / sessions.length) * 100 : 0\n\n return {\n totalSeconds,\n completedCount: completed.length,\n abandonedCount: abandoned.length,\n completionRate,\n }\n}\n\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/index.ts","../../src/utils/formatters.ts","../../src/utils/generators.ts","../../src/utils/validators.ts","../../src/constants/index.ts","../../src/utils/calculations.ts"],"sourcesContent":["/**\n * Utils Index\n *\n * Re-exports all utility functions.\n */\n\nexport * from \"./formatters\";\nexport * from \"./generators\";\nexport * from \"./validators\";\nexport * from \"./calculations\";\n","/**\n * Formatting Utilities\n *\n * Pure functions for formatting data.\n */\n\n/**\n * Format seconds into MM:SS format\n * @example formatTime(125) => \"02:05\"\n */\nexport function formatTime(seconds: number): string {\n const mins = Math.floor(seconds / 60)\n const secs = seconds % 60\n return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`\n}\n\n/**\n * Format seconds into human-readable total time\n * @example formatTotalTime(3700) => \"1h 1m\"\n * @example formatTotalTime(1800) => \"30m\"\n */\nexport function formatTotalTime(seconds: number): string {\n const hours = Math.floor(seconds / 3600)\n const mins = Math.floor((seconds % 3600) / 60)\n if (hours > 0) {\n return `${hours}h ${mins}m`\n }\n return `${mins}m`\n}\n\n/**\n * Format a number as currency\n * Uses Swiss German locale for authentic Swiss formatting (apostrophe as thousands separator)\n * @example formatCurrency(1234.56, 'CHF') => \"CHF 1'234.56\"\n */\nexport function formatCurrency(\n amount: number,\n currency: string,\n locale: string = 'de-CH'\n): string {\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(amount)\n}\n\n/**\n * Format a date as relative time (e.g., \"2h ago\", \"3d ago\")\n */\nexport function formatRelativeTime(dateStr: string): string {\n const date = new Date(dateStr)\n const now = new Date()\n const diffMs = now.getTime() - date.getTime()\n const diffMins = Math.floor(diffMs / 60000)\n const diffHours = Math.floor(diffMins / 60)\n const diffDays = Math.floor(diffHours / 24)\n\n if (diffMins < 1) return 'Just now'\n if (diffMins < 60) return `${diffMins}m ago`\n if (diffHours < 24) return `${diffHours}h ago`\n if (diffDays < 7) return `${diffDays}d ago`\n return date.toLocaleDateString()\n}\n\n/**\n * Format a date string to a readable format\n * @example formatDate('2024-01-15') => \"Jan 15, 2024\"\n */\nexport function formatDate(\n dateStr: string,\n options: Intl.DateTimeFormatOptions = {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n }\n): string {\n return new Date(dateStr).toLocaleDateString('en-US', options)\n}\n\n/**\n * Format a due date with context (Today, Tomorrow, Overdue, etc.)\n */\nexport function formatDueDate(dueDate: string | null): {\n text: string\n isOverdue: boolean\n} {\n if (!dueDate) return { text: '', isOverdue: false }\n\n const due = new Date(dueDate)\n const today = new Date()\n today.setHours(0, 0, 0, 0)\n\n const tomorrow = new Date(today)\n tomorrow.setDate(tomorrow.getDate() + 1)\n\n const dueDay = new Date(due)\n dueDay.setHours(0, 0, 0, 0)\n\n const isOverdue = dueDay < today\n\n if (dueDay.getTime() === today.getTime()) {\n return { text: 'Today', isOverdue: false }\n } else if (dueDay.getTime() === tomorrow.getTime()) {\n return { text: 'Tomorrow', isOverdue: false }\n } else if (isOverdue) {\n const daysAgo = Math.ceil(\n (today.getTime() - dueDay.getTime()) / (1000 * 60 * 60 * 24)\n )\n return { text: `${daysAgo}d overdue`, isOverdue: true }\n } else {\n return {\n text: due.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),\n isOverdue: false,\n }\n }\n}\n\n/**\n * Truncate a string with ellipsis\n * @example truncate(\"Hello World\", 5) => \"Hello...\"\n */\nexport function truncate(str: string, maxLength: number): string {\n if (str.length <= maxLength) return str\n return str.slice(0, maxLength) + '...'\n}\n\n/**\n * Extract repo name from GitHub URL\n * @example getRepoName(\"https://github.com/owner/repo\") => \"owner/repo\"\n */\nexport function getRepoName(url: string): string {\n const match = url.match(/github\\.com\\/(.+)$/)\n return match ? match[1] : url\n}\n\n","/**\n * ID and Data Generators\n *\n * Pure functions for generating IDs and data.\n */\n\n/**\n * Generate a unique ID\n * Uses crypto.randomUUID if available, falls back to timestamp-based ID\n *\n * Note: This works in both Node.js and browser environments.\n * React Native needs the fallback since crypto.randomUUID isn't available.\n */\nexport function generateId(): string {\n // Check if crypto.randomUUID is available (Node.js 19+, modern browsers)\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback: UUID v4-like implementation\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/**\n * Generate a short ID (timestamp + random)\n * Format: {timestamp}-{random7chars}\n * @example \"1732547123456-k8f3j2m\"\n */\nexport function generateShortId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n/**\n * Generate a random color in hex format\n */\nexport function generateRandomColor(): string {\n const colors = [\n '#ef4444', // red\n '#f97316', // orange\n '#eab308', // yellow\n '#22c55e', // green\n '#14b8a6', // teal\n '#3b82f6', // blue\n '#8b5cf6', // violet\n '#ec4899', // pink\n ]\n return colors[Math.floor(Math.random() * colors.length)]\n}\n\n","/**\n * Validation Utilities\n *\n * Pure functions for validating data.\n */\n\n/**\n * Check if a string is a valid email\n */\nexport function isValidEmail(email: string): boolean {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(email)\n}\n\n/**\n * Check if a string is a valid URL\n */\nexport function isValidUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Check if a string is a valid ISO date (YYYY-MM-DD)\n */\nexport function isValidISODate(dateStr: string): boolean {\n const regex = /^\\d{4}-\\d{2}-\\d{2}$/\n if (!regex.test(dateStr)) return false\n\n const date = new Date(dateStr)\n return !isNaN(date.getTime())\n}\n\n/**\n * Check if a value is a valid currency code\n */\nexport function isValidCurrency(currency: string): boolean {\n const validCurrencies = ['CHF', 'USD', 'EUR', 'PLN']\n return validCurrencies.includes(currency)\n}\n\n/**\n * Check if a value is a valid frequency\n */\nexport function isValidFrequency(frequency: string): boolean {\n const validFrequencies = ['monthly', 'yearly', '6-monthly', 'weekly', 'one-time']\n return validFrequencies.includes(frequency)\n}\n\n/**\n * Validate a positive number\n */\nexport function isPositiveNumber(value: unknown): value is number {\n return typeof value === 'number' && !isNaN(value) && value > 0\n}\n\n/**\n * Validate a non-empty string\n */\nexport function isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0\n}\n\n","/**\n * Shared Constants\n *\n * Common constants used across Vanaheim apps.\n */\n\n// ============================================================================\n// Currency\n// ============================================================================\n\nexport const CURRENCIES = ['CHF', 'USD', 'EUR', 'PLN'] as const\nexport type Currency = (typeof CURRENCIES)[number]\n\nexport const CURRENCY_SYMBOLS: Record<Currency, string> = {\n CHF: 'CHF',\n USD: '$',\n EUR: '€',\n PLN: 'zł',\n}\n\nexport const CURRENCY_NAMES: Record<Currency, string> = {\n CHF: 'Swiss Franc',\n USD: 'US Dollar',\n EUR: 'Euro',\n PLN: 'Polish Złoty',\n}\n\n// ============================================================================\n// Frequency\n// ============================================================================\n\nexport const FREQUENCIES = [\n 'monthly',\n 'yearly',\n '6-monthly',\n 'weekly',\n 'one-time',\n] as const\nexport type Frequency = (typeof FREQUENCIES)[number]\n\nexport const FREQUENCY_LABELS: Record<Frequency, string> = {\n monthly: 'Monthly',\n yearly: 'Yearly',\n '6-monthly': 'Every 6 Months',\n weekly: 'Weekly',\n 'one-time': 'One Time',\n}\n\nexport const FREQUENCY_MULTIPLIERS: Record<Frequency, number> = {\n monthly: 12,\n yearly: 1,\n '6-monthly': 2,\n weekly: 52,\n 'one-time': 1,\n}\n\n// ============================================================================\n// Focus\n// ============================================================================\n\nexport const DEFAULT_FOCUS_DURATIONS = [15, 25, 30, 45, 60, 90] as const\nexport type FocusDuration = (typeof DEFAULT_FOCUS_DURATIONS)[number]\n\nexport const FOCUS_STATUS = ['active', 'completed', 'abandoned'] as const\nexport type FocusStatus = (typeof FOCUS_STATUS)[number]\n\n// ============================================================================\n// Linear\n// ============================================================================\n\nexport const LINEAR_PRIORITIES = [0, 1, 2, 3, 4] as const\nexport type LinearPriorityValue = (typeof LINEAR_PRIORITIES)[number]\n\nexport const LINEAR_PRIORITY_COLORS: Record<LinearPriorityValue, string> = {\n 0: '#6b7280', // No priority - gray\n 1: '#ef4444', // Urgent - red\n 2: '#f97316', // High - orange\n 3: '#eab308', // Medium - yellow\n 4: '#3b82f6', // Low - blue\n}\n\n// ============================================================================\n// Cloud Agents\n// ============================================================================\n\nexport const CLOUD_AGENT_STATUSES = [\n 'CREATING',\n 'RUNNING',\n 'FINISHED',\n 'FAILED',\n 'CANCELLED',\n] as const\n\nexport const CLOUD_AGENT_STATUS_EMOJI: Record<string, string> = {\n CREATING: '🔨',\n RUNNING: '⏳',\n FINISHED: '✅',\n FAILED: '❌',\n CANCELLED: '🚫',\n}\n\nexport const CLOUD_AGENT_STATUS_COLORS: Record<string, string> = {\n CREATING: '#3b82f6',\n RUNNING: '#3b82f6',\n FINISHED: '#10b981',\n FAILED: '#ef4444',\n CANCELLED: '#6b7280',\n}\n\n// ============================================================================\n// API URLs\n// ============================================================================\n\nexport const API_URLS = {\n CURSOR_CLOUD: 'https://api.cursor.com',\n LINEAR_GRAPHQL: 'https://api.linear.app/graphql',\n} as const\n\n// ============================================================================\n// Settings Keys\n// ============================================================================\n\nexport const SETTING_KEYS = {\n // AI\n AI_MODEL: 'ai_model',\n AI_REASONING_ENABLED: 'ai_reasoning_enabled',\n\n // API Keys\n OPENAI_API_KEY: 'openai_api_key',\n ANTHROPIC_API_KEY: 'anthropic_api_key',\n CURSOR_API_KEY: 'cursor_api_key',\n LINEAR_API_KEY: 'linear_api_key',\n\n // Google\n GOOGLE_CLIENT_ID: 'google_client_id',\n GOOGLE_CLIENT_SECRET: 'google_client_secret',\n GOOGLE_CALENDAR_TOKENS: 'google_calendar_tokens',\n SELECTED_CALENDAR_IDS: 'selected_calendar_ids',\n} as const\n\nexport type SettingKey = (typeof SETTING_KEYS)[keyof typeof SETTING_KEYS]\n\n","/**\n * Calculation Utilities\n *\n * Pure functions for business logic calculations.\n */\n\nimport type { Expense, Income } from '../types/database'\nimport { FREQUENCY_MULTIPLIERS, type Frequency } from '../constants'\n\n/**\n * Convert an amount to yearly based on frequency\n */\nexport function toYearlyAmount(amount: number, frequency: Frequency): number {\n const multiplier = FREQUENCY_MULTIPLIERS[frequency] || 1\n return amount * multiplier\n}\n\n/**\n * Convert an amount to monthly based on frequency\n */\nexport function toMonthlyAmount(amount: number, frequency: Frequency): number {\n const yearly = toYearlyAmount(amount, frequency)\n return yearly / 12\n}\n\n/**\n * Calculate total expenses (monthly)\n */\nexport function calculateMonthlyExpenses(expenses: Expense[]): number {\n return expenses\n .filter((e) => e.isActive)\n .reduce((total, expense) => {\n const monthly = toMonthlyAmount(\n expense.amount,\n expense.frequency as Frequency\n )\n // Apply share percentage\n const myShare = (monthly * expense.sharePercentage) / 100\n return total + myShare\n }, 0)\n}\n\n/**\n * Calculate total income (monthly)\n */\nexport function calculateMonthlyIncome(incomes: Income[]): number {\n return incomes\n .filter((i) => i.isActive)\n .reduce((total, income) => {\n const monthly = toMonthlyAmount(income.amount, income.frequency as Frequency)\n return total + monthly\n }, 0)\n}\n\n/**\n * Calculate savings (income - expenses)\n */\nexport function calculateMonthlySavings(\n incomes: Income[],\n expenses: Expense[]\n): number {\n return calculateMonthlyIncome(incomes) - calculateMonthlyExpenses(expenses)\n}\n\n/**\n * Calculate savings rate as percentage\n */\nexport function calculateSavingsRate(\n incomes: Income[],\n expenses: Expense[]\n): number {\n const income = calculateMonthlyIncome(incomes)\n if (income === 0) return 0\n\n const savings = calculateMonthlySavings(incomes, expenses)\n return (savings / income) * 100\n}\n\n/**\n * Calculate lieu day balance\n */\nexport function calculateLieuBalance(\n lieuDays: Array<{ type: 'earned' | 'used' }>\n): number {\n return lieuDays.reduce((balance, day) => {\n return day.type === 'earned' ? balance + 1 : balance - 1\n }, 0)\n}\n\n/**\n * Calculate focus time stats for a period\n */\nexport function calculateFocusStats(\n sessions: Array<{ actualSeconds: number; status: string }>\n): {\n totalSeconds: number\n completedCount: number\n abandonedCount: number\n completionRate: number\n} {\n const completed = sessions.filter((s) => s.status === 'completed')\n const abandoned = sessions.filter((s) => s.status === 'abandoned')\n\n const totalSeconds = completed.reduce((sum, s) => sum + s.actualSeconds, 0)\n const completionRate =\n sessions.length > 0 ? (completed.length / sessions.length) * 100 : 0\n\n return {\n totalSeconds,\n completedCount: completed.length,\n abandonedCount: abandoned.length,\n completionRate,\n }\n}\n\n"],"mappings":"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","names":[]}
|
package/dist/utils/index.mjs
CHANGED
|
@@ -12,7 +12,7 @@ function formatTotalTime(seconds) {
|
|
|
12
12
|
}
|
|
13
13
|
return `${mins}m`;
|
|
14
14
|
}
|
|
15
|
-
function formatCurrency(amount, currency, locale = "
|
|
15
|
+
function formatCurrency(amount, currency, locale = "de-CH") {
|
|
16
16
|
return new Intl.NumberFormat(locale, {
|
|
17
17
|
style: "currency",
|
|
18
18
|
currency,
|
package/dist/utils/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/formatters.ts","../../src/utils/generators.ts","../../src/utils/validators.ts","../../src/constants/index.ts","../../src/utils/calculations.ts"],"sourcesContent":["/**\n * Formatting Utilities\n *\n * Pure functions for formatting data.\n */\n\n/**\n * Format seconds into MM:SS format\n * @example formatTime(125) => \"02:05\"\n */\nexport function formatTime(seconds: number): string {\n const mins = Math.floor(seconds / 60)\n const secs = seconds % 60\n return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`\n}\n\n/**\n * Format seconds into human-readable total time\n * @example formatTotalTime(3700) => \"1h 1m\"\n * @example formatTotalTime(1800) => \"30m\"\n */\nexport function formatTotalTime(seconds: number): string {\n const hours = Math.floor(seconds / 3600)\n const mins = Math.floor((seconds % 3600) / 60)\n if (hours > 0) {\n return `${hours}h ${mins}m`\n }\n return `${mins}m`\n}\n\n/**\n * Format a number as currency\n * @example formatCurrency(1234.56, 'CHF') => \"CHF 1,234.56\"\n */\nexport function formatCurrency(\n amount: number,\n currency: string,\n locale: string = 'en-CH'\n): string {\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(amount)\n}\n\n/**\n * Format a date as relative time (e.g., \"2h ago\", \"3d ago\")\n */\nexport function formatRelativeTime(dateStr: string): string {\n const date = new Date(dateStr)\n const now = new Date()\n const diffMs = now.getTime() - date.getTime()\n const diffMins = Math.floor(diffMs / 60000)\n const diffHours = Math.floor(diffMins / 60)\n const diffDays = Math.floor(diffHours / 24)\n\n if (diffMins < 1) return 'Just now'\n if (diffMins < 60) return `${diffMins}m ago`\n if (diffHours < 24) return `${diffHours}h ago`\n if (diffDays < 7) return `${diffDays}d ago`\n return date.toLocaleDateString()\n}\n\n/**\n * Format a date string to a readable format\n * @example formatDate('2024-01-15') => \"Jan 15, 2024\"\n */\nexport function formatDate(\n dateStr: string,\n options: Intl.DateTimeFormatOptions = {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n }\n): string {\n return new Date(dateStr).toLocaleDateString('en-US', options)\n}\n\n/**\n * Format a due date with context (Today, Tomorrow, Overdue, etc.)\n */\nexport function formatDueDate(dueDate: string | null): {\n text: string\n isOverdue: boolean\n} {\n if (!dueDate) return { text: '', isOverdue: false }\n\n const due = new Date(dueDate)\n const today = new Date()\n today.setHours(0, 0, 0, 0)\n\n const tomorrow = new Date(today)\n tomorrow.setDate(tomorrow.getDate() + 1)\n\n const dueDay = new Date(due)\n dueDay.setHours(0, 0, 0, 0)\n\n const isOverdue = dueDay < today\n\n if (dueDay.getTime() === today.getTime()) {\n return { text: 'Today', isOverdue: false }\n } else if (dueDay.getTime() === tomorrow.getTime()) {\n return { text: 'Tomorrow', isOverdue: false }\n } else if (isOverdue) {\n const daysAgo = Math.ceil(\n (today.getTime() - dueDay.getTime()) / (1000 * 60 * 60 * 24)\n )\n return { text: `${daysAgo}d overdue`, isOverdue: true }\n } else {\n return {\n text: due.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),\n isOverdue: false,\n }\n }\n}\n\n/**\n * Truncate a string with ellipsis\n * @example truncate(\"Hello World\", 5) => \"Hello...\"\n */\nexport function truncate(str: string, maxLength: number): string {\n if (str.length <= maxLength) return str\n return str.slice(0, maxLength) + '...'\n}\n\n/**\n * Extract repo name from GitHub URL\n * @example getRepoName(\"https://github.com/owner/repo\") => \"owner/repo\"\n */\nexport function getRepoName(url: string): string {\n const match = url.match(/github\\.com\\/(.+)$/)\n return match ? match[1] : url\n}\n\n","/**\n * ID and Data Generators\n *\n * Pure functions for generating IDs and data.\n */\n\n/**\n * Generate a unique ID\n * Uses crypto.randomUUID if available, falls back to timestamp-based ID\n *\n * Note: This works in both Node.js and browser environments.\n * React Native needs the fallback since crypto.randomUUID isn't available.\n */\nexport function generateId(): string {\n // Check if crypto.randomUUID is available (Node.js 19+, modern browsers)\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback: UUID v4-like implementation\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/**\n * Generate a short ID (timestamp + random)\n * Format: {timestamp}-{random7chars}\n * @example \"1732547123456-k8f3j2m\"\n */\nexport function generateShortId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n/**\n * Generate a random color in hex format\n */\nexport function generateRandomColor(): string {\n const colors = [\n '#ef4444', // red\n '#f97316', // orange\n '#eab308', // yellow\n '#22c55e', // green\n '#14b8a6', // teal\n '#3b82f6', // blue\n '#8b5cf6', // violet\n '#ec4899', // pink\n ]\n return colors[Math.floor(Math.random() * colors.length)]\n}\n\n","/**\n * Validation Utilities\n *\n * Pure functions for validating data.\n */\n\n/**\n * Check if a string is a valid email\n */\nexport function isValidEmail(email: string): boolean {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(email)\n}\n\n/**\n * Check if a string is a valid URL\n */\nexport function isValidUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Check if a string is a valid ISO date (YYYY-MM-DD)\n */\nexport function isValidISODate(dateStr: string): boolean {\n const regex = /^\\d{4}-\\d{2}-\\d{2}$/\n if (!regex.test(dateStr)) return false\n\n const date = new Date(dateStr)\n return !isNaN(date.getTime())\n}\n\n/**\n * Check if a value is a valid currency code\n */\nexport function isValidCurrency(currency: string): boolean {\n const validCurrencies = ['CHF', 'USD', 'EUR', 'PLN']\n return validCurrencies.includes(currency)\n}\n\n/**\n * Check if a value is a valid frequency\n */\nexport function isValidFrequency(frequency: string): boolean {\n const validFrequencies = ['monthly', 'yearly', '6-monthly', 'weekly', 'one-time']\n return validFrequencies.includes(frequency)\n}\n\n/**\n * Validate a positive number\n */\nexport function isPositiveNumber(value: unknown): value is number {\n return typeof value === 'number' && !isNaN(value) && value > 0\n}\n\n/**\n * Validate a non-empty string\n */\nexport function isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0\n}\n\n","/**\n * Shared Constants\n *\n * Common constants used across Vanaheim apps.\n */\n\n// ============================================================================\n// Currency\n// ============================================================================\n\nexport const CURRENCIES = ['CHF', 'USD', 'EUR', 'PLN'] as const\nexport type Currency = (typeof CURRENCIES)[number]\n\nexport const CURRENCY_SYMBOLS: Record<Currency, string> = {\n CHF: 'CHF',\n USD: '$',\n EUR: '€',\n PLN: 'zł',\n}\n\nexport const CURRENCY_NAMES: Record<Currency, string> = {\n CHF: 'Swiss Franc',\n USD: 'US Dollar',\n EUR: 'Euro',\n PLN: 'Polish Złoty',\n}\n\n// ============================================================================\n// Frequency\n// ============================================================================\n\nexport const FREQUENCIES = [\n 'monthly',\n 'yearly',\n '6-monthly',\n 'weekly',\n 'one-time',\n] as const\nexport type Frequency = (typeof FREQUENCIES)[number]\n\nexport const FREQUENCY_LABELS: Record<Frequency, string> = {\n monthly: 'Monthly',\n yearly: 'Yearly',\n '6-monthly': 'Every 6 Months',\n weekly: 'Weekly',\n 'one-time': 'One Time',\n}\n\nexport const FREQUENCY_MULTIPLIERS: Record<Frequency, number> = {\n monthly: 12,\n yearly: 1,\n '6-monthly': 2,\n weekly: 52,\n 'one-time': 1,\n}\n\n// ============================================================================\n// Focus\n// ============================================================================\n\nexport const DEFAULT_FOCUS_DURATIONS = [15, 25, 30, 45, 60, 90] as const\nexport type FocusDuration = (typeof DEFAULT_FOCUS_DURATIONS)[number]\n\nexport const FOCUS_STATUS = ['active', 'completed', 'abandoned'] as const\nexport type FocusStatus = (typeof FOCUS_STATUS)[number]\n\n// ============================================================================\n// Linear\n// ============================================================================\n\nexport const LINEAR_PRIORITIES = [0, 1, 2, 3, 4] as const\nexport type LinearPriorityValue = (typeof LINEAR_PRIORITIES)[number]\n\nexport const LINEAR_PRIORITY_COLORS: Record<LinearPriorityValue, string> = {\n 0: '#6b7280', // No priority - gray\n 1: '#ef4444', // Urgent - red\n 2: '#f97316', // High - orange\n 3: '#eab308', // Medium - yellow\n 4: '#3b82f6', // Low - blue\n}\n\n// ============================================================================\n// Cloud Agents\n// ============================================================================\n\nexport const CLOUD_AGENT_STATUSES = [\n 'CREATING',\n 'RUNNING',\n 'FINISHED',\n 'FAILED',\n 'CANCELLED',\n] as const\n\nexport const CLOUD_AGENT_STATUS_EMOJI: Record<string, string> = {\n CREATING: '🔨',\n RUNNING: '⏳',\n FINISHED: '✅',\n FAILED: '❌',\n CANCELLED: '🚫',\n}\n\nexport const CLOUD_AGENT_STATUS_COLORS: Record<string, string> = {\n CREATING: '#3b82f6',\n RUNNING: '#3b82f6',\n FINISHED: '#10b981',\n FAILED: '#ef4444',\n CANCELLED: '#6b7280',\n}\n\n// ============================================================================\n// API URLs\n// ============================================================================\n\nexport const API_URLS = {\n CURSOR_CLOUD: 'https://api.cursor.com',\n LINEAR_GRAPHQL: 'https://api.linear.app/graphql',\n} as const\n\n// ============================================================================\n// Settings Keys\n// ============================================================================\n\nexport const SETTING_KEYS = {\n // AI\n AI_MODEL: 'ai_model',\n AI_REASONING_ENABLED: 'ai_reasoning_enabled',\n\n // API Keys\n OPENAI_API_KEY: 'openai_api_key',\n ANTHROPIC_API_KEY: 'anthropic_api_key',\n CURSOR_API_KEY: 'cursor_api_key',\n LINEAR_API_KEY: 'linear_api_key',\n\n // Google\n GOOGLE_CLIENT_ID: 'google_client_id',\n GOOGLE_CLIENT_SECRET: 'google_client_secret',\n GOOGLE_CALENDAR_TOKENS: 'google_calendar_tokens',\n SELECTED_CALENDAR_IDS: 'selected_calendar_ids',\n} as const\n\nexport type SettingKey = (typeof SETTING_KEYS)[keyof typeof SETTING_KEYS]\n\n","/**\n * Calculation Utilities\n *\n * Pure functions for business logic calculations.\n */\n\nimport type { Expense, Income } from '../types/database'\nimport { FREQUENCY_MULTIPLIERS, type Frequency } from '../constants'\n\n/**\n * Convert an amount to yearly based on frequency\n */\nexport function toYearlyAmount(amount: number, frequency: Frequency): number {\n const multiplier = FREQUENCY_MULTIPLIERS[frequency] || 1\n return amount * multiplier\n}\n\n/**\n * Convert an amount to monthly based on frequency\n */\nexport function toMonthlyAmount(amount: number, frequency: Frequency): number {\n const yearly = toYearlyAmount(amount, frequency)\n return yearly / 12\n}\n\n/**\n * Calculate total expenses (monthly)\n */\nexport function calculateMonthlyExpenses(expenses: Expense[]): number {\n return expenses\n .filter((e) => e.isActive)\n .reduce((total, expense) => {\n const monthly = toMonthlyAmount(\n expense.amount,\n expense.frequency as Frequency\n )\n // Apply share percentage\n const myShare = (monthly * expense.sharePercentage) / 100\n return total + myShare\n }, 0)\n}\n\n/**\n * Calculate total income (monthly)\n */\nexport function calculateMonthlyIncome(incomes: Income[]): number {\n return incomes\n .filter((i) => i.isActive)\n .reduce((total, income) => {\n const monthly = toMonthlyAmount(income.amount, income.frequency as Frequency)\n return total + monthly\n }, 0)\n}\n\n/**\n * Calculate savings (income - expenses)\n */\nexport function calculateMonthlySavings(\n incomes: Income[],\n expenses: Expense[]\n): number {\n return calculateMonthlyIncome(incomes) - calculateMonthlyExpenses(expenses)\n}\n\n/**\n * Calculate savings rate as percentage\n */\nexport function calculateSavingsRate(\n incomes: Income[],\n expenses: Expense[]\n): number {\n const income = calculateMonthlyIncome(incomes)\n if (income === 0) return 0\n\n const savings = calculateMonthlySavings(incomes, expenses)\n return (savings / income) * 100\n}\n\n/**\n * Calculate lieu day balance\n */\nexport function calculateLieuBalance(\n lieuDays: Array<{ type: 'earned' | 'used' }>\n): number {\n return lieuDays.reduce((balance, day) => {\n return day.type === 'earned' ? balance + 1 : balance - 1\n }, 0)\n}\n\n/**\n * Calculate focus time stats for a period\n */\nexport function calculateFocusStats(\n sessions: Array<{ actualSeconds: number; status: string }>\n): {\n totalSeconds: number\n completedCount: number\n abandonedCount: number\n completionRate: number\n} {\n const completed = sessions.filter((s) => s.status === 'completed')\n const abandoned = sessions.filter((s) => s.status === 'abandoned')\n\n const totalSeconds = completed.reduce((sum, s) => sum + s.actualSeconds, 0)\n const completionRate =\n sessions.length > 0 ? (completed.length / sessions.length) * 100 : 0\n\n return {\n totalSeconds,\n completedCount: completed.length,\n abandonedCount: abandoned.length,\n completionRate,\n }\n}\n\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/formatters.ts","../../src/utils/generators.ts","../../src/utils/validators.ts","../../src/constants/index.ts","../../src/utils/calculations.ts"],"sourcesContent":["/**\n * Formatting Utilities\n *\n * Pure functions for formatting data.\n */\n\n/**\n * Format seconds into MM:SS format\n * @example formatTime(125) => \"02:05\"\n */\nexport function formatTime(seconds: number): string {\n const mins = Math.floor(seconds / 60)\n const secs = seconds % 60\n return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`\n}\n\n/**\n * Format seconds into human-readable total time\n * @example formatTotalTime(3700) => \"1h 1m\"\n * @example formatTotalTime(1800) => \"30m\"\n */\nexport function formatTotalTime(seconds: number): string {\n const hours = Math.floor(seconds / 3600)\n const mins = Math.floor((seconds % 3600) / 60)\n if (hours > 0) {\n return `${hours}h ${mins}m`\n }\n return `${mins}m`\n}\n\n/**\n * Format a number as currency\n * Uses Swiss German locale for authentic Swiss formatting (apostrophe as thousands separator)\n * @example formatCurrency(1234.56, 'CHF') => \"CHF 1'234.56\"\n */\nexport function formatCurrency(\n amount: number,\n currency: string,\n locale: string = 'de-CH'\n): string {\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(amount)\n}\n\n/**\n * Format a date as relative time (e.g., \"2h ago\", \"3d ago\")\n */\nexport function formatRelativeTime(dateStr: string): string {\n const date = new Date(dateStr)\n const now = new Date()\n const diffMs = now.getTime() - date.getTime()\n const diffMins = Math.floor(diffMs / 60000)\n const diffHours = Math.floor(diffMins / 60)\n const diffDays = Math.floor(diffHours / 24)\n\n if (diffMins < 1) return 'Just now'\n if (diffMins < 60) return `${diffMins}m ago`\n if (diffHours < 24) return `${diffHours}h ago`\n if (diffDays < 7) return `${diffDays}d ago`\n return date.toLocaleDateString()\n}\n\n/**\n * Format a date string to a readable format\n * @example formatDate('2024-01-15') => \"Jan 15, 2024\"\n */\nexport function formatDate(\n dateStr: string,\n options: Intl.DateTimeFormatOptions = {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n }\n): string {\n return new Date(dateStr).toLocaleDateString('en-US', options)\n}\n\n/**\n * Format a due date with context (Today, Tomorrow, Overdue, etc.)\n */\nexport function formatDueDate(dueDate: string | null): {\n text: string\n isOverdue: boolean\n} {\n if (!dueDate) return { text: '', isOverdue: false }\n\n const due = new Date(dueDate)\n const today = new Date()\n today.setHours(0, 0, 0, 0)\n\n const tomorrow = new Date(today)\n tomorrow.setDate(tomorrow.getDate() + 1)\n\n const dueDay = new Date(due)\n dueDay.setHours(0, 0, 0, 0)\n\n const isOverdue = dueDay < today\n\n if (dueDay.getTime() === today.getTime()) {\n return { text: 'Today', isOverdue: false }\n } else if (dueDay.getTime() === tomorrow.getTime()) {\n return { text: 'Tomorrow', isOverdue: false }\n } else if (isOverdue) {\n const daysAgo = Math.ceil(\n (today.getTime() - dueDay.getTime()) / (1000 * 60 * 60 * 24)\n )\n return { text: `${daysAgo}d overdue`, isOverdue: true }\n } else {\n return {\n text: due.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),\n isOverdue: false,\n }\n }\n}\n\n/**\n * Truncate a string with ellipsis\n * @example truncate(\"Hello World\", 5) => \"Hello...\"\n */\nexport function truncate(str: string, maxLength: number): string {\n if (str.length <= maxLength) return str\n return str.slice(0, maxLength) + '...'\n}\n\n/**\n * Extract repo name from GitHub URL\n * @example getRepoName(\"https://github.com/owner/repo\") => \"owner/repo\"\n */\nexport function getRepoName(url: string): string {\n const match = url.match(/github\\.com\\/(.+)$/)\n return match ? match[1] : url\n}\n\n","/**\n * ID and Data Generators\n *\n * Pure functions for generating IDs and data.\n */\n\n/**\n * Generate a unique ID\n * Uses crypto.randomUUID if available, falls back to timestamp-based ID\n *\n * Note: This works in both Node.js and browser environments.\n * React Native needs the fallback since crypto.randomUUID isn't available.\n */\nexport function generateId(): string {\n // Check if crypto.randomUUID is available (Node.js 19+, modern browsers)\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID()\n }\n\n // Fallback: UUID v4-like implementation\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/**\n * Generate a short ID (timestamp + random)\n * Format: {timestamp}-{random7chars}\n * @example \"1732547123456-k8f3j2m\"\n */\nexport function generateShortId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n/**\n * Generate a random color in hex format\n */\nexport function generateRandomColor(): string {\n const colors = [\n '#ef4444', // red\n '#f97316', // orange\n '#eab308', // yellow\n '#22c55e', // green\n '#14b8a6', // teal\n '#3b82f6', // blue\n '#8b5cf6', // violet\n '#ec4899', // pink\n ]\n return colors[Math.floor(Math.random() * colors.length)]\n}\n\n","/**\n * Validation Utilities\n *\n * Pure functions for validating data.\n */\n\n/**\n * Check if a string is a valid email\n */\nexport function isValidEmail(email: string): boolean {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(email)\n}\n\n/**\n * Check if a string is a valid URL\n */\nexport function isValidUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Check if a string is a valid ISO date (YYYY-MM-DD)\n */\nexport function isValidISODate(dateStr: string): boolean {\n const regex = /^\\d{4}-\\d{2}-\\d{2}$/\n if (!regex.test(dateStr)) return false\n\n const date = new Date(dateStr)\n return !isNaN(date.getTime())\n}\n\n/**\n * Check if a value is a valid currency code\n */\nexport function isValidCurrency(currency: string): boolean {\n const validCurrencies = ['CHF', 'USD', 'EUR', 'PLN']\n return validCurrencies.includes(currency)\n}\n\n/**\n * Check if a value is a valid frequency\n */\nexport function isValidFrequency(frequency: string): boolean {\n const validFrequencies = ['monthly', 'yearly', '6-monthly', 'weekly', 'one-time']\n return validFrequencies.includes(frequency)\n}\n\n/**\n * Validate a positive number\n */\nexport function isPositiveNumber(value: unknown): value is number {\n return typeof value === 'number' && !isNaN(value) && value > 0\n}\n\n/**\n * Validate a non-empty string\n */\nexport function isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0\n}\n\n","/**\n * Shared Constants\n *\n * Common constants used across Vanaheim apps.\n */\n\n// ============================================================================\n// Currency\n// ============================================================================\n\nexport const CURRENCIES = ['CHF', 'USD', 'EUR', 'PLN'] as const\nexport type Currency = (typeof CURRENCIES)[number]\n\nexport const CURRENCY_SYMBOLS: Record<Currency, string> = {\n CHF: 'CHF',\n USD: '$',\n EUR: '€',\n PLN: 'zł',\n}\n\nexport const CURRENCY_NAMES: Record<Currency, string> = {\n CHF: 'Swiss Franc',\n USD: 'US Dollar',\n EUR: 'Euro',\n PLN: 'Polish Złoty',\n}\n\n// ============================================================================\n// Frequency\n// ============================================================================\n\nexport const FREQUENCIES = [\n 'monthly',\n 'yearly',\n '6-monthly',\n 'weekly',\n 'one-time',\n] as const\nexport type Frequency = (typeof FREQUENCIES)[number]\n\nexport const FREQUENCY_LABELS: Record<Frequency, string> = {\n monthly: 'Monthly',\n yearly: 'Yearly',\n '6-monthly': 'Every 6 Months',\n weekly: 'Weekly',\n 'one-time': 'One Time',\n}\n\nexport const FREQUENCY_MULTIPLIERS: Record<Frequency, number> = {\n monthly: 12,\n yearly: 1,\n '6-monthly': 2,\n weekly: 52,\n 'one-time': 1,\n}\n\n// ============================================================================\n// Focus\n// ============================================================================\n\nexport const DEFAULT_FOCUS_DURATIONS = [15, 25, 30, 45, 60, 90] as const\nexport type FocusDuration = (typeof DEFAULT_FOCUS_DURATIONS)[number]\n\nexport const FOCUS_STATUS = ['active', 'completed', 'abandoned'] as const\nexport type FocusStatus = (typeof FOCUS_STATUS)[number]\n\n// ============================================================================\n// Linear\n// ============================================================================\n\nexport const LINEAR_PRIORITIES = [0, 1, 2, 3, 4] as const\nexport type LinearPriorityValue = (typeof LINEAR_PRIORITIES)[number]\n\nexport const LINEAR_PRIORITY_COLORS: Record<LinearPriorityValue, string> = {\n 0: '#6b7280', // No priority - gray\n 1: '#ef4444', // Urgent - red\n 2: '#f97316', // High - orange\n 3: '#eab308', // Medium - yellow\n 4: '#3b82f6', // Low - blue\n}\n\n// ============================================================================\n// Cloud Agents\n// ============================================================================\n\nexport const CLOUD_AGENT_STATUSES = [\n 'CREATING',\n 'RUNNING',\n 'FINISHED',\n 'FAILED',\n 'CANCELLED',\n] as const\n\nexport const CLOUD_AGENT_STATUS_EMOJI: Record<string, string> = {\n CREATING: '🔨',\n RUNNING: '⏳',\n FINISHED: '✅',\n FAILED: '❌',\n CANCELLED: '🚫',\n}\n\nexport const CLOUD_AGENT_STATUS_COLORS: Record<string, string> = {\n CREATING: '#3b82f6',\n RUNNING: '#3b82f6',\n FINISHED: '#10b981',\n FAILED: '#ef4444',\n CANCELLED: '#6b7280',\n}\n\n// ============================================================================\n// API URLs\n// ============================================================================\n\nexport const API_URLS = {\n CURSOR_CLOUD: 'https://api.cursor.com',\n LINEAR_GRAPHQL: 'https://api.linear.app/graphql',\n} as const\n\n// ============================================================================\n// Settings Keys\n// ============================================================================\n\nexport const SETTING_KEYS = {\n // AI\n AI_MODEL: 'ai_model',\n AI_REASONING_ENABLED: 'ai_reasoning_enabled',\n\n // API Keys\n OPENAI_API_KEY: 'openai_api_key',\n ANTHROPIC_API_KEY: 'anthropic_api_key',\n CURSOR_API_KEY: 'cursor_api_key',\n LINEAR_API_KEY: 'linear_api_key',\n\n // Google\n GOOGLE_CLIENT_ID: 'google_client_id',\n GOOGLE_CLIENT_SECRET: 'google_client_secret',\n GOOGLE_CALENDAR_TOKENS: 'google_calendar_tokens',\n SELECTED_CALENDAR_IDS: 'selected_calendar_ids',\n} as const\n\nexport type SettingKey = (typeof SETTING_KEYS)[keyof typeof SETTING_KEYS]\n\n","/**\n * Calculation Utilities\n *\n * Pure functions for business logic calculations.\n */\n\nimport type { Expense, Income } from '../types/database'\nimport { FREQUENCY_MULTIPLIERS, type Frequency } from '../constants'\n\n/**\n * Convert an amount to yearly based on frequency\n */\nexport function toYearlyAmount(amount: number, frequency: Frequency): number {\n const multiplier = FREQUENCY_MULTIPLIERS[frequency] || 1\n return amount * multiplier\n}\n\n/**\n * Convert an amount to monthly based on frequency\n */\nexport function toMonthlyAmount(amount: number, frequency: Frequency): number {\n const yearly = toYearlyAmount(amount, frequency)\n return yearly / 12\n}\n\n/**\n * Calculate total expenses (monthly)\n */\nexport function calculateMonthlyExpenses(expenses: Expense[]): number {\n return expenses\n .filter((e) => e.isActive)\n .reduce((total, expense) => {\n const monthly = toMonthlyAmount(\n expense.amount,\n expense.frequency as Frequency\n )\n // Apply share percentage\n const myShare = (monthly * expense.sharePercentage) / 100\n return total + myShare\n }, 0)\n}\n\n/**\n * Calculate total income (monthly)\n */\nexport function calculateMonthlyIncome(incomes: Income[]): number {\n return incomes\n .filter((i) => i.isActive)\n .reduce((total, income) => {\n const monthly = toMonthlyAmount(income.amount, income.frequency as Frequency)\n return total + monthly\n }, 0)\n}\n\n/**\n * Calculate savings (income - expenses)\n */\nexport function calculateMonthlySavings(\n incomes: Income[],\n expenses: Expense[]\n): number {\n return calculateMonthlyIncome(incomes) - calculateMonthlyExpenses(expenses)\n}\n\n/**\n * Calculate savings rate as percentage\n */\nexport function calculateSavingsRate(\n incomes: Income[],\n expenses: Expense[]\n): number {\n const income = calculateMonthlyIncome(incomes)\n if (income === 0) return 0\n\n const savings = calculateMonthlySavings(incomes, expenses)\n return (savings / income) * 100\n}\n\n/**\n * Calculate lieu day balance\n */\nexport function calculateLieuBalance(\n lieuDays: Array<{ type: 'earned' | 'used' }>\n): number {\n return lieuDays.reduce((balance, day) => {\n return day.type === 'earned' ? balance + 1 : balance - 1\n }, 0)\n}\n\n/**\n * Calculate focus time stats for a period\n */\nexport function calculateFocusStats(\n sessions: Array<{ actualSeconds: number; status: string }>\n): {\n totalSeconds: number\n completedCount: number\n abandonedCount: number\n completionRate: number\n} {\n const completed = sessions.filter((s) => s.status === 'completed')\n const abandoned = sessions.filter((s) => s.status === 'abandoned')\n\n const totalSeconds = completed.reduce((sum, s) => sum + s.actualSeconds, 0)\n const completionRate =\n sessions.length > 0 ? (completed.length / sessions.length) * 100 : 0\n\n return {\n totalSeconds,\n completedCount: completed.length,\n abandonedCount: abandoned.length,\n completionRate,\n }\n}\n\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrck-labs/vanaheim-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Shared types, constants, and utilities for Vanaheim apps",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -25,6 +25,21 @@
|
|
|
25
25
|
"types": "./dist/constants/index.d.ts",
|
|
26
26
|
"import": "./dist/constants/index.mjs",
|
|
27
27
|
"require": "./dist/constants/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./query": {
|
|
30
|
+
"types": "./dist/query/index.d.ts",
|
|
31
|
+
"import": "./dist/query/index.mjs",
|
|
32
|
+
"require": "./dist/query/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./atoms": {
|
|
35
|
+
"types": "./dist/atoms/index.d.ts",
|
|
36
|
+
"import": "./dist/atoms/index.mjs",
|
|
37
|
+
"require": "./dist/atoms/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./date": {
|
|
40
|
+
"types": "./dist/date/index.d.ts",
|
|
41
|
+
"import": "./dist/date/index.mjs",
|
|
42
|
+
"require": "./dist/date/index.js"
|
|
28
43
|
}
|
|
29
44
|
},
|
|
30
45
|
"files": [
|
|
@@ -58,8 +73,17 @@
|
|
|
58
73
|
"publishConfig": {
|
|
59
74
|
"access": "public"
|
|
60
75
|
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"jotai": "^2.0.0"
|
|
78
|
+
},
|
|
79
|
+
"peerDependenciesMeta": {
|
|
80
|
+
"jotai": {
|
|
81
|
+
"optional": true
|
|
82
|
+
}
|
|
83
|
+
},
|
|
61
84
|
"devDependencies": {
|
|
62
85
|
"@changesets/cli": "^2.27.0",
|
|
86
|
+
"jotai": "^2.10.3",
|
|
63
87
|
"tsup": "^8.0.0",
|
|
64
88
|
"typescript": "^5.5.0"
|
|
65
89
|
}
|