@serhii.olishevskyi/docs 1.1.3 → 1.1.5

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  UserPopoverComponent,
3
3
  UserPopoverComponent_default
4
- } from "./chunk-BAFKGCJJ.mjs";
4
+ } from "./chunk-UA57YNRJ.mjs";
5
5
  export {
6
6
  UserPopoverComponent,
7
7
  UserPopoverComponent_default as default
@@ -200,6 +200,84 @@ var getDeviceId = () => {
200
200
  };
201
201
 
202
202
  // src/services/session.service.ts
203
+ var fetchCompanySessions = async (supabase, companyId, page = 1, pageSize = 5, searchQuery = "") => {
204
+ const currentDeviceId = getDeviceId();
205
+ let query = supabase.from("memberships").select(`
206
+ user_id,
207
+ role,
208
+ employee_post,
209
+ profiles (
210
+ id,
211
+ display_name,
212
+ first_name,
213
+ last_name
214
+ )
215
+ `, { count: "exact" }).eq("company_id", companyId);
216
+ if (searchQuery.trim()) {
217
+ const q = `%${searchQuery.trim()}%`;
218
+ const { data: matchedProfiles } = await supabase.from("profiles").select("id").or(`display_name.ilike.${q},first_name.ilike.${q},last_name.ilike.${q}`);
219
+ const matchedUserIds = (matchedProfiles || []).map((p) => p.id);
220
+ if (matchedUserIds.length > 0) {
221
+ query = query.or(`employee_post.ilike.${q},user_id.in.(${matchedUserIds.join(",")})`);
222
+ } else {
223
+ query = query.ilike("employee_post", q);
224
+ }
225
+ }
226
+ const { data: members, error: membersError } = await query;
227
+ if (membersError) throw membersError;
228
+ if (!members || members.length === 0) {
229
+ return { data: [], totalCount: 0 };
230
+ }
231
+ const userMap = /* @__PURE__ */ new Map();
232
+ members.forEach((m) => {
233
+ const profile = Array.isArray(m.profiles) ? m.profiles[0] : m.profiles;
234
+ const name = profile?.display_name || [profile?.first_name, profile?.last_name].filter(Boolean).join(" ") || "\u041D\u0435\u0432\u0456\u0434\u043E\u043C\u0438\u0439 \u043A\u043E\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447";
235
+ userMap.set(m.user_id, {
236
+ name,
237
+ post: m.employee_post || "\u041F\u043E\u0441\u0430\u0434\u0430 \u043D\u0435 \u0432\u043A\u0430\u0437\u0430\u043D\u0430",
238
+ role: m.role || "user"
239
+ });
240
+ });
241
+ const userIds = Array.from(userMap.keys());
242
+ const { data: sessions, error: sessionsError } = await supabase.from("user_sessions").select("*").in("user_id", userIds).order("last_seen_at", { ascending: false });
243
+ if (sessionsError) throw sessionsError;
244
+ const groupsMap = /* @__PURE__ */ new Map();
245
+ (sessions || []).forEach((s) => {
246
+ const userInfo = userMap.get(s.user_id);
247
+ if (!userInfo) return;
248
+ if (!groupsMap.has(s.user_id)) {
249
+ groupsMap.set(s.user_id, {
250
+ userId: s.user_id,
251
+ userName: userInfo.name,
252
+ employeePost: userInfo.post,
253
+ role: userInfo.role,
254
+ sessions: []
255
+ });
256
+ }
257
+ groupsMap.get(s.user_id).sessions.push({
258
+ id: s.id,
259
+ user_id: s.user_id,
260
+ user_agent: s.user_agent,
261
+ ip_address: s.ip_address,
262
+ created_at: s.created_at,
263
+ last_seen_at: s.last_seen_at,
264
+ device_id: s.device_id,
265
+ is_current: s.device_id === currentDeviceId,
266
+ user_name: userInfo.name,
267
+ employee_post: userInfo.post,
268
+ role: userInfo.role,
269
+ url: s.url
270
+ });
271
+ });
272
+ const allGroupedWithSessions = Array.from(groupsMap.values()).filter((group) => group.sessions.length > 0).sort((a, b) => a.userName.localeCompare(b.userName, "uk"));
273
+ const totalCount = allGroupedWithSessions.length;
274
+ const from = (page - 1) * pageSize;
275
+ const paginatedData = allGroupedWithSessions.slice(from, from + pageSize);
276
+ return {
277
+ data: paginatedData,
278
+ totalCount
279
+ };
280
+ };
203
281
  var trackUserSession = async (supabase, userId) => {
204
282
  const deviceId = getDeviceId();
205
283
  let ip = "";
@@ -222,6 +300,30 @@ var trackUserSession = async (supabase, userId) => {
222
300
  { onConflict: "user_id, device_id" }
223
301
  );
224
302
  };
303
+ var trackUserSessionByRoute = async (supabase, userId, currentUrl) => {
304
+ const deviceId = getDeviceId();
305
+ await supabase.from("user_sessions").upsert(
306
+ {
307
+ user_id: userId,
308
+ device_id: deviceId,
309
+ user_agent: navigator.userAgent,
310
+ url: currentUrl || window.location.href,
311
+ last_seen_at: (/* @__PURE__ */ new Date()).toISOString()
312
+ },
313
+ { onConflict: "user_id, device_id" }
314
+ );
315
+ };
316
+ var fetchUserSessions = async (supabase) => {
317
+ const currentDeviceId = getDeviceId();
318
+ const { data: { user } } = await supabase.auth.getUser();
319
+ if (!user) return [];
320
+ const { data, error } = await supabase.from("user_sessions").select("*").eq("user_id", user.id).order("last_seen_at", { ascending: false });
321
+ if (error) throw error;
322
+ return (data || []).map((s) => ({
323
+ ...s,
324
+ is_current: s.device_id === currentDeviceId
325
+ }));
326
+ };
225
327
  var isCurrentSessionValid = async (supabase, userId) => {
226
328
  try {
227
329
  const currentDeviceId = getDeviceId();
@@ -237,11 +339,42 @@ var isCurrentSessionValid = async (supabase, userId) => {
237
339
  return true;
238
340
  }
239
341
  };
342
+ var groupSessionsByUser = (sessions) => {
343
+ const groupsMap = /* @__PURE__ */ new Map();
344
+ sessions.forEach((session) => {
345
+ const userId = session.user_id;
346
+ if (!groupsMap.has(userId)) {
347
+ groupsMap.set(userId, {
348
+ userId,
349
+ userName: session.user_name || "\u041D\u0435\u0432\u0456\u0434\u043E\u043C\u0438\u0439 \u043A\u043E\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447",
350
+ employeePost: session.employee_post || "\u041F\u043E\u0441\u0430\u0434\u0430 \u043D\u0435 \u0432\u043A\u0430\u0437\u0430\u043D\u0430",
351
+ role: session.role || "user",
352
+ sessions: []
353
+ });
354
+ }
355
+ groupsMap.get(userId).sessions.push(session);
356
+ });
357
+ return Array.from(groupsMap.values()).sort(
358
+ (a, b) => a.userName.localeCompare(b.userName, "uk")
359
+ );
360
+ };
240
361
  var getUserLastSeenAt = async (supabase, userId) => {
241
362
  const { data, error } = await supabase.from("user_sessions").select("last_seen_at").eq("user_id", userId).order("last_seen_at", { ascending: false }).limit(1).maybeSingle();
242
363
  if (error || !data) return null;
243
364
  return data.last_seen_at;
244
365
  };
366
+ var getUsersLastSeenMap = async (supabase, userIds) => {
367
+ if (!userIds.length) return {};
368
+ const { data, error } = await supabase.from("user_sessions").select("user_id, last_seen_at").in("user_id", userIds).order("last_seen_at", { ascending: false });
369
+ if (error || !data) return {};
370
+ const resultMap = {};
371
+ for (const session of data) {
372
+ if (!resultMap[session.user_id]) {
373
+ resultMap[session.user_id] = session.last_seen_at;
374
+ }
375
+ }
376
+ return resultMap;
377
+ };
245
378
 
246
379
  // src/context/AuthProvider.tsx
247
380
  import { jsx as jsx3 } from "react/jsx-runtime";
@@ -2413,6 +2546,14 @@ var UserPopoverComponent = ({
2413
2546
  var UserPopoverComponent_default = UserPopoverComponent;
2414
2547
 
2415
2548
  export {
2549
+ fetchCompanySessions,
2550
+ trackUserSession,
2551
+ trackUserSessionByRoute,
2552
+ fetchUserSessions,
2553
+ isCurrentSessionValid,
2554
+ groupSessionsByUser,
2555
+ getUserLastSeenAt,
2556
+ getUsersLastSeenMap,
2416
2557
  SupabaseProvider,
2417
2558
  useSupabase,
2418
2559
  AuthProvider,
package/dist/index.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as React$1 from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
3
  import { TextFieldProps, IconButtonProps, TooltipProps, SxProps, Theme } from '@mui/material';
4
+ import * as _supabase_supabase_js from '@supabase/supabase-js';
4
5
  import { SupabaseClient, User, Session } from '@supabase/supabase-js';
5
6
 
6
7
  interface UrlValue {
@@ -488,4 +489,43 @@ declare const Providers: {
488
489
  readonly AIProvider: React$1.FC<AIProviderProps>;
489
490
  };
490
491
 
491
- export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, AuthProvider, BreadcrumbProvider, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps as CopyClipboardButtonProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps as EmailInputProps, type Language, LocalizationProvider, type MyAvatarComponentProps as MyAvatarProps, type NumberInputComponentProps as NumberInputProps, Providers, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps as TextFieldProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps as TranslatableTooltipProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps as UserAvatarProps, type UserPopoverComponentProps as UserPopoverProps, type UserRoleChipComponentProps as UserRoleChipProps, UserSettingsProvider, type UserStatusChipComponentProps as UserStatusChipProps, useAI, useAppSettings, useAppTheme, useAuth, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace, useUserSettings };
492
+ interface UserSessionItem {
493
+ id: string;
494
+ user_id: string;
495
+ user_agent: string | null;
496
+ ip_address: string | null;
497
+ created_at: string;
498
+ last_seen_at: string;
499
+ device_id: string;
500
+ url?: string | null;
501
+ is_current?: boolean;
502
+ user_name?: string;
503
+ employee_post?: string;
504
+ role?: string;
505
+ }
506
+ interface UserGroupedSessions {
507
+ userId: string;
508
+ userName: string;
509
+ employeePost: string;
510
+ role: string;
511
+ sessions: UserSessionItem[];
512
+ }
513
+ interface FetchCompanySessionsResponse {
514
+ data: UserGroupedSessions[];
515
+ totalCount: number;
516
+ }
517
+
518
+ declare const Services: {
519
+ readonly SessionService: {
520
+ readonly fetchCompanySessions: (supabase: _supabase_supabase_js.SupabaseClient, companyId: string, page?: number, pageSize?: number, searchQuery?: string) => Promise<FetchCompanySessionsResponse>;
521
+ readonly trackUserSession: (supabase: _supabase_supabase_js.SupabaseClient, userId: string) => Promise<void>;
522
+ readonly trackUserSessionByRoute: (supabase: _supabase_supabase_js.SupabaseClient, userId: string, currentUrl?: string) => Promise<void>;
523
+ readonly fetchUserSessions: (supabase: _supabase_supabase_js.SupabaseClient) => Promise<UserSessionItem[]>;
524
+ readonly isCurrentSessionValid: (supabase: _supabase_supabase_js.SupabaseClient, userId: string) => Promise<boolean>;
525
+ readonly groupSessionsByUser: (sessions: UserSessionItem[]) => UserGroupedSessions[];
526
+ readonly getUserLastSeenAt: (supabase: _supabase_supabase_js.SupabaseClient, userId: string) => Promise<string | null>;
527
+ readonly getUsersLastSeenMap: (supabase: _supabase_supabase_js.SupabaseClient, userIds: string[]) => Promise<Record<string, string>>;
528
+ };
529
+ };
530
+
531
+ export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, AuthProvider, BreadcrumbProvider, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps as CopyClipboardButtonProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps as EmailInputProps, type Language, LocalizationProvider, type MyAvatarComponentProps as MyAvatarProps, type NumberInputComponentProps as NumberInputProps, Providers, Services, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps as TextFieldProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps as TranslatableTooltipProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps as UserAvatarProps, type UserGroupedSessions, type UserPopoverComponentProps as UserPopoverProps, type UserRoleChipComponentProps as UserRoleChipProps, type UserSessionItem, UserSettingsProvider, type UserStatusChipComponentProps as UserStatusChipProps, useAI, useAppSettings, useAppTheme, useAuth, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace, useUserSettings };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as React$1 from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
3
  import { TextFieldProps, IconButtonProps, TooltipProps, SxProps, Theme } from '@mui/material';
4
+ import * as _supabase_supabase_js from '@supabase/supabase-js';
4
5
  import { SupabaseClient, User, Session } from '@supabase/supabase-js';
5
6
 
6
7
  interface UrlValue {
@@ -488,4 +489,43 @@ declare const Providers: {
488
489
  readonly AIProvider: React$1.FC<AIProviderProps>;
489
490
  };
490
491
 
491
- export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, AuthProvider, BreadcrumbProvider, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps as CopyClipboardButtonProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps as EmailInputProps, type Language, LocalizationProvider, type MyAvatarComponentProps as MyAvatarProps, type NumberInputComponentProps as NumberInputProps, Providers, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps as TextFieldProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps as TranslatableTooltipProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps as UserAvatarProps, type UserPopoverComponentProps as UserPopoverProps, type UserRoleChipComponentProps as UserRoleChipProps, UserSettingsProvider, type UserStatusChipComponentProps as UserStatusChipProps, useAI, useAppSettings, useAppTheme, useAuth, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace, useUserSettings };
492
+ interface UserSessionItem {
493
+ id: string;
494
+ user_id: string;
495
+ user_agent: string | null;
496
+ ip_address: string | null;
497
+ created_at: string;
498
+ last_seen_at: string;
499
+ device_id: string;
500
+ url?: string | null;
501
+ is_current?: boolean;
502
+ user_name?: string;
503
+ employee_post?: string;
504
+ role?: string;
505
+ }
506
+ interface UserGroupedSessions {
507
+ userId: string;
508
+ userName: string;
509
+ employeePost: string;
510
+ role: string;
511
+ sessions: UserSessionItem[];
512
+ }
513
+ interface FetchCompanySessionsResponse {
514
+ data: UserGroupedSessions[];
515
+ totalCount: number;
516
+ }
517
+
518
+ declare const Services: {
519
+ readonly SessionService: {
520
+ readonly fetchCompanySessions: (supabase: _supabase_supabase_js.SupabaseClient, companyId: string, page?: number, pageSize?: number, searchQuery?: string) => Promise<FetchCompanySessionsResponse>;
521
+ readonly trackUserSession: (supabase: _supabase_supabase_js.SupabaseClient, userId: string) => Promise<void>;
522
+ readonly trackUserSessionByRoute: (supabase: _supabase_supabase_js.SupabaseClient, userId: string, currentUrl?: string) => Promise<void>;
523
+ readonly fetchUserSessions: (supabase: _supabase_supabase_js.SupabaseClient) => Promise<UserSessionItem[]>;
524
+ readonly isCurrentSessionValid: (supabase: _supabase_supabase_js.SupabaseClient, userId: string) => Promise<boolean>;
525
+ readonly groupSessionsByUser: (sessions: UserSessionItem[]) => UserGroupedSessions[];
526
+ readonly getUserLastSeenAt: (supabase: _supabase_supabase_js.SupabaseClient, userId: string) => Promise<string | null>;
527
+ readonly getUsersLastSeenMap: (supabase: _supabase_supabase_js.SupabaseClient, userIds: string[]) => Promise<Record<string, string>>;
528
+ };
529
+ };
530
+
531
+ export { type AIConfig, type AIGenerateOptions, AIProvider, type AIProviderProps, type AIProviderType, AppSettingsProvider, AuthProvider, BreadcrumbProvider, type ConfirmOptions, type ConfirmResult, ConfirmationProvider, type ConfirmationProviderProps, type CopyClipboardButtonComponentProps as CopyClipboardButtonProps, type DialogSeverity, type DisplayState, type EmailInputComponentProps as EmailInputProps, type Language, LocalizationProvider, type MyAvatarComponentProps as MyAvatarProps, type NumberInputComponentProps as NumberInputProps, Providers, Services, type SupabaseConfig, SupabaseProvider, type SupabaseProviderProps, type TextFieldComponentProps as TextFieldProps, type ThemeColors, type ThemeConfig, type ThemeMode, ThemeProvider, ToastProvider, type ToastType, type TranslatableTooltipComponentProps as TranslatableTooltipProps, TranslateProvider, TranslateText, TranslationEditProvider, type TypographyConfig, UI, type UrlComponentProps as UrlProps, type UserAvatarComponentProps as UserAvatarProps, type UserGroupedSessions, type UserPopoverComponentProps as UserPopoverProps, type UserRoleChipComponentProps as UserRoleChipProps, type UserSessionItem, UserSettingsProvider, type UserStatusChipComponentProps as UserStatusChipProps, useAI, useAppSettings, useAppTheme, useAuth, useBreadcrumb, useConfirmation, useLocalization, useSupabase, useToast, useTranslationEdit, useTranslationNamespace, useUserSettings };
package/dist/index.js CHANGED
@@ -169,11 +169,89 @@ var init_deviceId = __esm({
169
169
  });
170
170
 
171
171
  // src/services/session.service.ts
172
- var trackUserSession, isCurrentSessionValid, getUserLastSeenAt;
172
+ var fetchCompanySessions, trackUserSession, trackUserSessionByRoute, fetchUserSessions, isCurrentSessionValid, groupSessionsByUser, getUserLastSeenAt, getUsersLastSeenMap;
173
173
  var init_session_service = __esm({
174
174
  "src/services/session.service.ts"() {
175
175
  "use strict";
176
176
  init_deviceId();
177
+ fetchCompanySessions = async (supabase, companyId, page = 1, pageSize = 5, searchQuery = "") => {
178
+ const currentDeviceId = getDeviceId();
179
+ let query = supabase.from("memberships").select(`
180
+ user_id,
181
+ role,
182
+ employee_post,
183
+ profiles (
184
+ id,
185
+ display_name,
186
+ first_name,
187
+ last_name
188
+ )
189
+ `, { count: "exact" }).eq("company_id", companyId);
190
+ if (searchQuery.trim()) {
191
+ const q = `%${searchQuery.trim()}%`;
192
+ const { data: matchedProfiles } = await supabase.from("profiles").select("id").or(`display_name.ilike.${q},first_name.ilike.${q},last_name.ilike.${q}`);
193
+ const matchedUserIds = (matchedProfiles || []).map((p) => p.id);
194
+ if (matchedUserIds.length > 0) {
195
+ query = query.or(`employee_post.ilike.${q},user_id.in.(${matchedUserIds.join(",")})`);
196
+ } else {
197
+ query = query.ilike("employee_post", q);
198
+ }
199
+ }
200
+ const { data: members, error: membersError } = await query;
201
+ if (membersError) throw membersError;
202
+ if (!members || members.length === 0) {
203
+ return { data: [], totalCount: 0 };
204
+ }
205
+ const userMap = /* @__PURE__ */ new Map();
206
+ members.forEach((m) => {
207
+ const profile = Array.isArray(m.profiles) ? m.profiles[0] : m.profiles;
208
+ const name = profile?.display_name || [profile?.first_name, profile?.last_name].filter(Boolean).join(" ") || "\u041D\u0435\u0432\u0456\u0434\u043E\u043C\u0438\u0439 \u043A\u043E\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447";
209
+ userMap.set(m.user_id, {
210
+ name,
211
+ post: m.employee_post || "\u041F\u043E\u0441\u0430\u0434\u0430 \u043D\u0435 \u0432\u043A\u0430\u0437\u0430\u043D\u0430",
212
+ role: m.role || "user"
213
+ });
214
+ });
215
+ const userIds = Array.from(userMap.keys());
216
+ const { data: sessions, error: sessionsError } = await supabase.from("user_sessions").select("*").in("user_id", userIds).order("last_seen_at", { ascending: false });
217
+ if (sessionsError) throw sessionsError;
218
+ const groupsMap = /* @__PURE__ */ new Map();
219
+ (sessions || []).forEach((s) => {
220
+ const userInfo = userMap.get(s.user_id);
221
+ if (!userInfo) return;
222
+ if (!groupsMap.has(s.user_id)) {
223
+ groupsMap.set(s.user_id, {
224
+ userId: s.user_id,
225
+ userName: userInfo.name,
226
+ employeePost: userInfo.post,
227
+ role: userInfo.role,
228
+ sessions: []
229
+ });
230
+ }
231
+ groupsMap.get(s.user_id).sessions.push({
232
+ id: s.id,
233
+ user_id: s.user_id,
234
+ user_agent: s.user_agent,
235
+ ip_address: s.ip_address,
236
+ created_at: s.created_at,
237
+ last_seen_at: s.last_seen_at,
238
+ device_id: s.device_id,
239
+ is_current: s.device_id === currentDeviceId,
240
+ user_name: userInfo.name,
241
+ employee_post: userInfo.post,
242
+ role: userInfo.role,
243
+ url: s.url
244
+ });
245
+ });
246
+ const allGroupedWithSessions = Array.from(groupsMap.values()).filter((group) => group.sessions.length > 0).sort((a, b) => a.userName.localeCompare(b.userName, "uk"));
247
+ const totalCount = allGroupedWithSessions.length;
248
+ const from = (page - 1) * pageSize;
249
+ const paginatedData = allGroupedWithSessions.slice(from, from + pageSize);
250
+ return {
251
+ data: paginatedData,
252
+ totalCount
253
+ };
254
+ };
177
255
  trackUserSession = async (supabase, userId) => {
178
256
  const deviceId = getDeviceId();
179
257
  let ip = "";
@@ -196,6 +274,30 @@ var init_session_service = __esm({
196
274
  { onConflict: "user_id, device_id" }
197
275
  );
198
276
  };
277
+ trackUserSessionByRoute = async (supabase, userId, currentUrl) => {
278
+ const deviceId = getDeviceId();
279
+ await supabase.from("user_sessions").upsert(
280
+ {
281
+ user_id: userId,
282
+ device_id: deviceId,
283
+ user_agent: navigator.userAgent,
284
+ url: currentUrl || window.location.href,
285
+ last_seen_at: (/* @__PURE__ */ new Date()).toISOString()
286
+ },
287
+ { onConflict: "user_id, device_id" }
288
+ );
289
+ };
290
+ fetchUserSessions = async (supabase) => {
291
+ const currentDeviceId = getDeviceId();
292
+ const { data: { user } } = await supabase.auth.getUser();
293
+ if (!user) return [];
294
+ const { data, error } = await supabase.from("user_sessions").select("*").eq("user_id", user.id).order("last_seen_at", { ascending: false });
295
+ if (error) throw error;
296
+ return (data || []).map((s) => ({
297
+ ...s,
298
+ is_current: s.device_id === currentDeviceId
299
+ }));
300
+ };
199
301
  isCurrentSessionValid = async (supabase, userId) => {
200
302
  try {
201
303
  const currentDeviceId = getDeviceId();
@@ -211,11 +313,42 @@ var init_session_service = __esm({
211
313
  return true;
212
314
  }
213
315
  };
316
+ groupSessionsByUser = (sessions) => {
317
+ const groupsMap = /* @__PURE__ */ new Map();
318
+ sessions.forEach((session) => {
319
+ const userId = session.user_id;
320
+ if (!groupsMap.has(userId)) {
321
+ groupsMap.set(userId, {
322
+ userId,
323
+ userName: session.user_name || "\u041D\u0435\u0432\u0456\u0434\u043E\u043C\u0438\u0439 \u043A\u043E\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447",
324
+ employeePost: session.employee_post || "\u041F\u043E\u0441\u0430\u0434\u0430 \u043D\u0435 \u0432\u043A\u0430\u0437\u0430\u043D\u0430",
325
+ role: session.role || "user",
326
+ sessions: []
327
+ });
328
+ }
329
+ groupsMap.get(userId).sessions.push(session);
330
+ });
331
+ return Array.from(groupsMap.values()).sort(
332
+ (a, b) => a.userName.localeCompare(b.userName, "uk")
333
+ );
334
+ };
214
335
  getUserLastSeenAt = async (supabase, userId) => {
215
336
  const { data, error } = await supabase.from("user_sessions").select("last_seen_at").eq("user_id", userId).order("last_seen_at", { ascending: false }).limit(1).maybeSingle();
216
337
  if (error || !data) return null;
217
338
  return data.last_seen_at;
218
339
  };
340
+ getUsersLastSeenMap = async (supabase, userIds) => {
341
+ if (!userIds.length) return {};
342
+ const { data, error } = await supabase.from("user_sessions").select("user_id, last_seen_at").in("user_id", userIds).order("last_seen_at", { ascending: false });
343
+ if (error || !data) return {};
344
+ const resultMap = {};
345
+ for (const session of data) {
346
+ if (!resultMap[session.user_id]) {
347
+ resultMap[session.user_id] = session.last_seen_at;
348
+ }
349
+ }
350
+ return resultMap;
351
+ };
219
352
  }
220
353
  });
221
354
 
@@ -2617,6 +2750,7 @@ __export(index_exports, {
2617
2750
  ConfirmationProvider: () => ConfirmationProvider,
2618
2751
  LocalizationProvider: () => LocalizationProvider,
2619
2752
  Providers: () => Providers,
2753
+ Services: () => Services,
2620
2754
  SupabaseProvider: () => SupabaseProvider,
2621
2755
  ThemeProvider: () => ThemeProvider,
2622
2756
  ToastProvider: () => ToastProvider,
@@ -3875,6 +4009,21 @@ var UI = {
3875
4009
  // src/index.ts
3876
4010
  init_context();
3877
4011
  init_context();
4012
+
4013
+ // src/services/index.ts
4014
+ init_session_service();
4015
+ var Services = {
4016
+ SessionService: {
4017
+ fetchCompanySessions,
4018
+ trackUserSession,
4019
+ trackUserSessionByRoute,
4020
+ fetchUserSessions,
4021
+ isCurrentSessionValid,
4022
+ groupSessionsByUser,
4023
+ getUserLastSeenAt,
4024
+ getUsersLastSeenMap
4025
+ }
4026
+ };
3878
4027
  // Annotate the CommonJS export names for ESM import in node:
3879
4028
  0 && (module.exports = {
3880
4029
  AIProvider,
@@ -3884,6 +4033,7 @@ init_context();
3884
4033
  ConfirmationProvider,
3885
4034
  LocalizationProvider,
3886
4035
  Providers,
4036
+ Services,
3887
4037
  SupabaseProvider,
3888
4038
  ThemeProvider,
3889
4039
  ToastProvider,
package/dist/index.mjs CHANGED
@@ -19,6 +19,14 @@ import {
19
19
  UserRoleChipComponent,
20
20
  UserSettingsProvider,
21
21
  UserStatusChipComponent,
22
+ fetchCompanySessions,
23
+ fetchUserSessions,
24
+ getUserLastSeenAt,
25
+ getUsersLastSeenMap,
26
+ groupSessionsByUser,
27
+ isCurrentSessionValid,
28
+ trackUserSession,
29
+ trackUserSessionByRoute,
22
30
  translationService,
23
31
  useAI,
24
32
  useAppSettings,
@@ -32,7 +40,7 @@ import {
32
40
  useTranslationEdit,
33
41
  useTranslationNamespace,
34
42
  useUserSettings
35
- } from "./chunk-BAFKGCJJ.mjs";
43
+ } from "./chunk-UA57YNRJ.mjs";
36
44
 
37
45
  // src/UI/components/inputs/TextFieldComponent.tsx
38
46
  import { useState } from "react";
@@ -730,7 +738,7 @@ var EmailInputComponent = ({
730
738
  import { useState as useState5, useEffect as useEffect2, lazy, Suspense } from "react";
731
739
  import { Avatar, Tooltip as Tooltip3, Typography as Typography2 } from "@mui/material";
732
740
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
733
- var LazyUserPopover = lazy(() => import("./UserPopoverComponent-64KAMDBO.mjs"));
741
+ var LazyUserPopover = lazy(() => import("./UserPopoverComponent-5F4S3X4F.mjs"));
734
742
  var getInitials = (name) => {
735
743
  if (!name) return "?";
736
744
  const parts = name.trim().split(" ");
@@ -1266,6 +1274,20 @@ var UI = {
1266
1274
  PublicLanguageSelector
1267
1275
  }
1268
1276
  };
1277
+
1278
+ // src/services/index.ts
1279
+ var Services = {
1280
+ SessionService: {
1281
+ fetchCompanySessions,
1282
+ trackUserSession,
1283
+ trackUserSessionByRoute,
1284
+ fetchUserSessions,
1285
+ isCurrentSessionValid,
1286
+ groupSessionsByUser,
1287
+ getUserLastSeenAt,
1288
+ getUsersLastSeenMap
1289
+ }
1290
+ };
1269
1291
  export {
1270
1292
  AIProvider,
1271
1293
  AppSettingsProvider,
@@ -1274,6 +1296,7 @@ export {
1274
1296
  ConfirmationProvider,
1275
1297
  LocalizationProvider,
1276
1298
  Providers,
1299
+ Services,
1277
1300
  SupabaseProvider,
1278
1301
  ThemeProvider,
1279
1302
  ToastProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serhii.olishevskyi/docs",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "DOCS.UA react packages",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",