@adventurelabs/scout-core 1.4.66 → 1.4.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers/users.js +3 -14
- package/dist/index.d.ts +1 -1
- package/dist/providers/ScoutRefreshProvider.d.ts +7 -0
- package/dist/types/db.d.ts +4 -1
- package/dist/types/supabase.d.ts +7 -0
- package/package.json +1 -1
- package/dist/helpers/components.d.ts +0 -14
- package/dist/helpers/components.js +0 -155
- package/dist/helpers/operator.d.ts +0 -5
- package/dist/helpers/operator.js +0 -64
- package/dist/hooks/useHerdData.d.ts +0 -167
- package/dist/hooks/useHerdData.js +0 -153
- package/dist/hooks/useScoutBrowserClient.d.ts +0 -11
- package/dist/hooks/useScoutBrowserClient.js +0 -17
- package/dist/hooks/useScoutDbListener.d.ts +0 -3
- package/dist/hooks/useScoutDbListener.js +0 -134
- package/dist/hooks/useScoutRealtimeConnectivity copy.d.ts +0 -3
- package/dist/hooks/useScoutRealtimeConnectivity copy.js +0 -159
- package/dist/hooks/useScoutRealtimeDevices copy.d.ts +0 -3
- package/dist/hooks/useScoutRealtimeDevices copy.js +0 -55
- package/dist/types/data.d.ts +0 -4
- package/dist/types/data.js +0 -1
package/dist/helpers/users.js
CHANGED
|
@@ -31,14 +31,7 @@ export async function server_get_users_with_herd_access(herd_id, supabaseClient)
|
|
|
31
31
|
.from("users_roles_per_herd")
|
|
32
32
|
.select(`
|
|
33
33
|
role,
|
|
34
|
-
users (
|
|
35
|
-
id,
|
|
36
|
-
username,
|
|
37
|
-
earthranger_id,
|
|
38
|
-
first,
|
|
39
|
-
last,
|
|
40
|
-
title
|
|
41
|
-
)
|
|
34
|
+
users (*)
|
|
42
35
|
`)
|
|
43
36
|
.eq("herd_id", herd_id);
|
|
44
37
|
if (error) {
|
|
@@ -49,7 +42,6 @@ export async function server_get_users_with_herd_access(herd_id, supabaseClient)
|
|
|
49
42
|
};
|
|
50
43
|
}
|
|
51
44
|
else {
|
|
52
|
-
// Transform the data to match IUserAndRole interface
|
|
53
45
|
const transformedData = data.map((item) => ({
|
|
54
46
|
user: item.users,
|
|
55
47
|
role: item.role,
|
|
@@ -76,12 +68,9 @@ export async function server_upsert_user_with_role(herd_id, username, role) {
|
|
|
76
68
|
herd_id: herd_id,
|
|
77
69
|
role: role,
|
|
78
70
|
};
|
|
79
|
-
|
|
80
|
-
const { data, error } = await supabase
|
|
81
|
-
.from("users_roles_per_herd")
|
|
82
|
-
.upsert(newRoleForDb);
|
|
71
|
+
const { error } = await supabase.from("users_roles_per_herd").upsert(newRoleForDb);
|
|
83
72
|
if (error) {
|
|
84
73
|
return IWebResponse.error("Unable to update or add user with provided username. Ensure you have sufficient permissions.").to_compatible();
|
|
85
74
|
}
|
|
86
|
-
return IWebResponse.success(
|
|
75
|
+
return IWebResponse.success({ user, role }).to_compatible();
|
|
87
76
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -70,5 +70,5 @@ export * from "./supabase/middleware";
|
|
|
70
70
|
export * from "./supabase/server";
|
|
71
71
|
export * from "./api_keys/actions";
|
|
72
72
|
export type { HerdModule, IHerdModule } from "./types/herd_module";
|
|
73
|
-
export type { IDevice, IEvent, IUser, IHerd, IHerdPrettyLocation, IEventWithTags, IZoneWithActions, ICredential, CredentialInsert, CredentialUpdate, ICertificate, CertificateInsert, CertificateUpdate, IUserAndRole, IApiKeyScout, ILayer, IHeartbeat, IProvider, IConnectivity, ISession, ISessionWithCoordinates, IConnectivityWithCoordinates, IObservation, ObservationInsert, ObservationUpdate, IAnalysisJob, IAnalysisTask, AnalysisWorkStatus, } from "./types/db";
|
|
73
|
+
export type { IDevice, IEvent, IUser, IHerd, IHerdPrettyLocation, IEventWithTags, IZoneWithActions, ICredential, CredentialInsert, CredentialUpdate, ICertificate, CertificateInsert, CertificateUpdate, IUserAndRole, IUserProfileRow, IApiKeyScout, ILayer, IHeartbeat, IProvider, IConnectivity, ISession, ISessionWithCoordinates, IConnectivityWithCoordinates, IObservation, ObservationInsert, ObservationUpdate, IAnalysisJob, IAnalysisTask, AnalysisWorkStatus, } from "./types/db";
|
|
74
74
|
export { EnumSessionsVisibility } from "./types/events";
|
|
@@ -1366,26 +1366,32 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
|
|
|
1366
1366
|
};
|
|
1367
1367
|
users: {
|
|
1368
1368
|
Row: {
|
|
1369
|
+
auto_approve_sessions: boolean;
|
|
1369
1370
|
earthranger_id: string | null;
|
|
1370
1371
|
first: string | null;
|
|
1371
1372
|
id: string;
|
|
1372
1373
|
last: string | null;
|
|
1374
|
+
signature_base64: string | null;
|
|
1373
1375
|
title: string | null;
|
|
1374
1376
|
username: string | null;
|
|
1375
1377
|
};
|
|
1376
1378
|
Insert: {
|
|
1379
|
+
auto_approve_sessions?: boolean;
|
|
1377
1380
|
earthranger_id?: string | null;
|
|
1378
1381
|
first?: string | null;
|
|
1379
1382
|
id: string;
|
|
1380
1383
|
last?: string | null;
|
|
1384
|
+
signature_base64?: string | null;
|
|
1381
1385
|
title?: string | null;
|
|
1382
1386
|
username?: string | null;
|
|
1383
1387
|
};
|
|
1384
1388
|
Update: {
|
|
1389
|
+
auto_approve_sessions?: boolean;
|
|
1385
1390
|
earthranger_id?: string | null;
|
|
1386
1391
|
first?: string | null;
|
|
1387
1392
|
id?: string;
|
|
1388
1393
|
last?: string | null;
|
|
1394
|
+
signature_base64?: string | null;
|
|
1389
1395
|
title?: string | null;
|
|
1390
1396
|
username?: string | null;
|
|
1391
1397
|
};
|
|
@@ -2860,6 +2866,7 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
|
|
|
2860
2866
|
description: string | null;
|
|
2861
2867
|
latitude: number | null;
|
|
2862
2868
|
longitude: number | null;
|
|
2869
|
+
color: string | null;
|
|
2863
2870
|
};
|
|
2864
2871
|
embedding_match: {
|
|
2865
2872
|
id: number | null;
|
package/dist/types/db.d.ts
CHANGED
|
@@ -6,7 +6,10 @@ export type DeviceType = Database["public"]["Enums"]["device_type"];
|
|
|
6
6
|
export type MediaType = Database["public"]["Enums"]["media_type"];
|
|
7
7
|
export type TagObservationType = Database["public"]["Enums"]["tag_observation_type"];
|
|
8
8
|
export type AnalysisWorkStatus = Database["public"]["Enums"]["analysis_work_status"];
|
|
9
|
+
/** Supabase Auth user (`auth.getUser()`); not `public.users`. */
|
|
9
10
|
export type IUser = User;
|
|
11
|
+
/** Full `public.users` profile row (generated from DB). */
|
|
12
|
+
export type IUserProfileRow = Database["public"]["Tables"]["users"]["Row"];
|
|
10
13
|
export type IDevice = Database["public"]["CompositeTypes"]["device_pretty_location"] & {
|
|
11
14
|
api_keys_scout?: IApiKeyScout[];
|
|
12
15
|
parts?: IPart[];
|
|
@@ -131,7 +134,7 @@ export interface IEventWithSession extends IEvent {
|
|
|
131
134
|
session: ISession | null;
|
|
132
135
|
}
|
|
133
136
|
export type IUserAndRole = {
|
|
134
|
-
user:
|
|
137
|
+
user: IUserProfileRow | null;
|
|
135
138
|
role: Role;
|
|
136
139
|
};
|
|
137
140
|
export interface IApiKeyScout {
|
package/dist/types/supabase.d.ts
CHANGED
|
@@ -1436,26 +1436,32 @@ export type Database = {
|
|
|
1436
1436
|
};
|
|
1437
1437
|
users: {
|
|
1438
1438
|
Row: {
|
|
1439
|
+
auto_approve_sessions: boolean;
|
|
1439
1440
|
earthranger_id: string | null;
|
|
1440
1441
|
first: string | null;
|
|
1441
1442
|
id: string;
|
|
1442
1443
|
last: string | null;
|
|
1444
|
+
signature_base64: string | null;
|
|
1443
1445
|
title: string | null;
|
|
1444
1446
|
username: string | null;
|
|
1445
1447
|
};
|
|
1446
1448
|
Insert: {
|
|
1449
|
+
auto_approve_sessions?: boolean;
|
|
1447
1450
|
earthranger_id?: string | null;
|
|
1448
1451
|
first?: string | null;
|
|
1449
1452
|
id: string;
|
|
1450
1453
|
last?: string | null;
|
|
1454
|
+
signature_base64?: string | null;
|
|
1451
1455
|
title?: string | null;
|
|
1452
1456
|
username?: string | null;
|
|
1453
1457
|
};
|
|
1454
1458
|
Update: {
|
|
1459
|
+
auto_approve_sessions?: boolean;
|
|
1455
1460
|
earthranger_id?: string | null;
|
|
1456
1461
|
first?: string | null;
|
|
1457
1462
|
id?: string;
|
|
1458
1463
|
last?: string | null;
|
|
1464
|
+
signature_base64?: string | null;
|
|
1459
1465
|
title?: string | null;
|
|
1460
1466
|
username?: string | null;
|
|
1461
1467
|
};
|
|
@@ -2945,6 +2951,7 @@ export type Database = {
|
|
|
2945
2951
|
description: string | null;
|
|
2946
2952
|
latitude: number | null;
|
|
2947
2953
|
longitude: number | null;
|
|
2954
|
+
color: string | null;
|
|
2948
2955
|
};
|
|
2949
2956
|
embedding_match: {
|
|
2950
2957
|
id: number | null;
|
package/package.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Database } from "../types/supabase";
|
|
2
|
-
import { IComponent, ComponentInsert } from "../types/db";
|
|
3
|
-
import { IWebResponseCompatible } from "../types/requests";
|
|
4
|
-
import { SupabaseClient } from "@supabase/supabase-js";
|
|
5
|
-
export declare function get_components_by_device_id(client: SupabaseClient<Database>, device_id: number): Promise<IWebResponseCompatible<IComponent[]>>;
|
|
6
|
-
export declare function get_component_by_id(client: SupabaseClient<Database>, component_id: number): Promise<IWebResponseCompatible<IComponent | null>>;
|
|
7
|
-
export declare function get_components_by_serial_number(client: SupabaseClient<Database>, serial_number: string): Promise<IWebResponseCompatible<IComponent[]>>;
|
|
8
|
-
export declare function get_components_by_product_number(client: SupabaseClient<Database>, product_number: string): Promise<IWebResponseCompatible<IComponent[]>>;
|
|
9
|
-
export declare function get_components_by_status(client: SupabaseClient<Database>, status: Database["public"]["Enums"]["component_status"]): Promise<IWebResponseCompatible<IComponent[]>>;
|
|
10
|
-
export declare function create_component(client: SupabaseClient<Database>, newComponent: ComponentInsert): Promise<IWebResponseCompatible<IComponent | null>>;
|
|
11
|
-
export declare function update_component(client: SupabaseClient<Database>, component_id: number, updatedComponent: Partial<ComponentInsert>): Promise<IWebResponseCompatible<IComponent | null>>;
|
|
12
|
-
export declare function delete_component(client: SupabaseClient<Database>, component_id: number): Promise<IWebResponseCompatible<IComponent | null>>;
|
|
13
|
-
export declare function update_component_status(client: SupabaseClient<Database>, component_id: number, status: Database["public"]["Enums"]["component_status"]): Promise<IWebResponseCompatible<IComponent | null>>;
|
|
14
|
-
export declare function get_components_by_certificate_id(client: SupabaseClient<Database>, certificate_id: number): Promise<IWebResponseCompatible<IComponent[]>>;
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { IWebResponse } from "../types/requests";
|
|
2
|
-
export async function get_components_by_device_id(client, device_id) {
|
|
3
|
-
const { data, error } = await client
|
|
4
|
-
.from("components")
|
|
5
|
-
.select("*")
|
|
6
|
-
.eq("device_id", device_id)
|
|
7
|
-
.order("created_at", { ascending: false });
|
|
8
|
-
if (error) {
|
|
9
|
-
return IWebResponse.error(error.message).to_compatible();
|
|
10
|
-
}
|
|
11
|
-
if (!data) {
|
|
12
|
-
return IWebResponse.error("No components found for device").to_compatible();
|
|
13
|
-
}
|
|
14
|
-
return IWebResponse.success(data).to_compatible();
|
|
15
|
-
}
|
|
16
|
-
export async function get_component_by_id(client, component_id) {
|
|
17
|
-
const { data, error } = await client
|
|
18
|
-
.from("components")
|
|
19
|
-
.select("*")
|
|
20
|
-
.eq("id", component_id)
|
|
21
|
-
.single();
|
|
22
|
-
if (error) {
|
|
23
|
-
return IWebResponse.error(error.message).to_compatible();
|
|
24
|
-
}
|
|
25
|
-
if (!data) {
|
|
26
|
-
return IWebResponse.error("Component not found").to_compatible();
|
|
27
|
-
}
|
|
28
|
-
return IWebResponse.success(data).to_compatible();
|
|
29
|
-
}
|
|
30
|
-
export async function get_components_by_serial_number(client, serial_number) {
|
|
31
|
-
const { data, error } = await client
|
|
32
|
-
.from("components")
|
|
33
|
-
.select("*")
|
|
34
|
-
.eq("serial_number", serial_number)
|
|
35
|
-
.order("created_at", { ascending: false });
|
|
36
|
-
if (error) {
|
|
37
|
-
return IWebResponse.error(error.message).to_compatible();
|
|
38
|
-
}
|
|
39
|
-
if (!data) {
|
|
40
|
-
return IWebResponse.error(`No components found with serial number: ${serial_number}`).to_compatible();
|
|
41
|
-
}
|
|
42
|
-
return IWebResponse.success(data).to_compatible();
|
|
43
|
-
}
|
|
44
|
-
export async function get_components_by_product_number(client, product_number) {
|
|
45
|
-
const { data, error } = await client
|
|
46
|
-
.from("components")
|
|
47
|
-
.select("*")
|
|
48
|
-
.eq("product_number", product_number)
|
|
49
|
-
.order("created_at", { ascending: false });
|
|
50
|
-
if (error) {
|
|
51
|
-
return IWebResponse.error(error.message).to_compatible();
|
|
52
|
-
}
|
|
53
|
-
if (!data) {
|
|
54
|
-
return IWebResponse.error(`No components found with product number: ${product_number}`).to_compatible();
|
|
55
|
-
}
|
|
56
|
-
return IWebResponse.success(data).to_compatible();
|
|
57
|
-
}
|
|
58
|
-
export async function get_components_by_status(client, status) {
|
|
59
|
-
const { data, error } = await client
|
|
60
|
-
.from("components")
|
|
61
|
-
.select("*")
|
|
62
|
-
.eq("status", status)
|
|
63
|
-
.order("created_at", { ascending: false });
|
|
64
|
-
if (error) {
|
|
65
|
-
return IWebResponse.error(error.message).to_compatible();
|
|
66
|
-
}
|
|
67
|
-
if (!data) {
|
|
68
|
-
return IWebResponse.error(`No components found with status: ${status}`).to_compatible();
|
|
69
|
-
}
|
|
70
|
-
return IWebResponse.success(data).to_compatible();
|
|
71
|
-
}
|
|
72
|
-
export async function create_component(client, newComponent) {
|
|
73
|
-
// Validate required fields
|
|
74
|
-
if (!newComponent.device_id) {
|
|
75
|
-
return IWebResponse.error("Device ID is required").to_compatible();
|
|
76
|
-
}
|
|
77
|
-
if (!newComponent.serial_number) {
|
|
78
|
-
return IWebResponse.error("Serial number is required").to_compatible();
|
|
79
|
-
}
|
|
80
|
-
const { data, error } = await client
|
|
81
|
-
.from("components")
|
|
82
|
-
.insert([newComponent])
|
|
83
|
-
.select("*")
|
|
84
|
-
.single();
|
|
85
|
-
if (error) {
|
|
86
|
-
return IWebResponse.error(error.message).to_compatible();
|
|
87
|
-
}
|
|
88
|
-
if (!data) {
|
|
89
|
-
return IWebResponse.error("Failed to create component").to_compatible();
|
|
90
|
-
}
|
|
91
|
-
return IWebResponse.success(data).to_compatible();
|
|
92
|
-
}
|
|
93
|
-
export async function update_component(client, component_id, updatedComponent) {
|
|
94
|
-
// Remove fields that shouldn't be updated
|
|
95
|
-
const updateData = { ...updatedComponent };
|
|
96
|
-
delete updateData.id;
|
|
97
|
-
delete updateData.created_at;
|
|
98
|
-
const { data, error } = await client
|
|
99
|
-
.from("components")
|
|
100
|
-
.update(updateData)
|
|
101
|
-
.eq("id", component_id)
|
|
102
|
-
.select("*")
|
|
103
|
-
.single();
|
|
104
|
-
if (error) {
|
|
105
|
-
return IWebResponse.error(error.message).to_compatible();
|
|
106
|
-
}
|
|
107
|
-
if (!data) {
|
|
108
|
-
return IWebResponse.error("Component not found or update failed").to_compatible();
|
|
109
|
-
}
|
|
110
|
-
return IWebResponse.success(data).to_compatible();
|
|
111
|
-
}
|
|
112
|
-
export async function delete_component(client, component_id) {
|
|
113
|
-
const { data, error } = await client
|
|
114
|
-
.from("components")
|
|
115
|
-
.delete()
|
|
116
|
-
.eq("id", component_id)
|
|
117
|
-
.select("*")
|
|
118
|
-
.single();
|
|
119
|
-
if (error) {
|
|
120
|
-
return IWebResponse.error(error.message).to_compatible();
|
|
121
|
-
}
|
|
122
|
-
if (!data) {
|
|
123
|
-
return IWebResponse.error("Component not found or deletion failed").to_compatible();
|
|
124
|
-
}
|
|
125
|
-
return IWebResponse.success(data).to_compatible();
|
|
126
|
-
}
|
|
127
|
-
export async function update_component_status(client, component_id, status) {
|
|
128
|
-
const { data, error } = await client
|
|
129
|
-
.from("components")
|
|
130
|
-
.update({ status })
|
|
131
|
-
.eq("id", component_id)
|
|
132
|
-
.select("*")
|
|
133
|
-
.single();
|
|
134
|
-
if (error) {
|
|
135
|
-
return IWebResponse.error(error.message).to_compatible();
|
|
136
|
-
}
|
|
137
|
-
if (!data) {
|
|
138
|
-
return IWebResponse.error("Component not found or status update failed").to_compatible();
|
|
139
|
-
}
|
|
140
|
-
return IWebResponse.success(data).to_compatible();
|
|
141
|
-
}
|
|
142
|
-
export async function get_components_by_certificate_id(client, certificate_id) {
|
|
143
|
-
const { data, error } = await client
|
|
144
|
-
.from("components")
|
|
145
|
-
.select("*")
|
|
146
|
-
.eq("certificate_id", certificate_id)
|
|
147
|
-
.order("created_at", { ascending: false });
|
|
148
|
-
if (error) {
|
|
149
|
-
return IWebResponse.error(error.message).to_compatible();
|
|
150
|
-
}
|
|
151
|
-
if (!data) {
|
|
152
|
-
return IWebResponse.error(`No components found with certificate ID: ${certificate_id}`).to_compatible();
|
|
153
|
-
}
|
|
154
|
-
return IWebResponse.success(data).to_compatible();
|
|
155
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { IWebResponseCompatible } from "../types/requests";
|
|
2
|
-
import { IOperator } from "../types/db";
|
|
3
|
-
export declare function server_get_operators_by_session_id(sessionId: number): Promise<IWebResponseCompatible<IOperator[]>>;
|
|
4
|
-
export declare function server_get_operators_by_user_id(userId: string): Promise<IWebResponseCompatible<IOperator[]>>;
|
|
5
|
-
export declare function server_get_operators_by_session_id_filtered(sessionId: number, action?: string, timestampAfter?: string): Promise<IWebResponseCompatible<IOperator[]>>;
|
package/dist/helpers/operator.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"use server";
|
|
2
|
-
import { newServerClient } from "../supabase/server";
|
|
3
|
-
import { EnumWebResponse, IWebResponse, } from "../types/requests";
|
|
4
|
-
// Get operators by session id (server id)
|
|
5
|
-
export async function server_get_operators_by_session_id(sessionId) {
|
|
6
|
-
const supabase = await newServerClient();
|
|
7
|
-
const { data, error } = await supabase
|
|
8
|
-
.from("operators")
|
|
9
|
-
.select("*")
|
|
10
|
-
.eq("session_id", sessionId)
|
|
11
|
-
.order("created_at", { ascending: false });
|
|
12
|
-
if (error) {
|
|
13
|
-
console.warn("Error fetching operators by session id:", error.message);
|
|
14
|
-
return {
|
|
15
|
-
status: EnumWebResponse.ERROR,
|
|
16
|
-
msg: error.message,
|
|
17
|
-
data: [],
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
return IWebResponse.success(data || []).to_compatible();
|
|
21
|
-
}
|
|
22
|
-
// Get all operators for a specific user
|
|
23
|
-
export async function server_get_operators_by_user_id(userId) {
|
|
24
|
-
const supabase = await newServerClient();
|
|
25
|
-
const { data, error } = await supabase
|
|
26
|
-
.from("operators")
|
|
27
|
-
.select("*")
|
|
28
|
-
.eq("user_id", userId)
|
|
29
|
-
.order("created_at", { ascending: false });
|
|
30
|
-
if (error) {
|
|
31
|
-
console.warn("Error fetching operators by user id:", error.message);
|
|
32
|
-
return {
|
|
33
|
-
status: EnumWebResponse.ERROR,
|
|
34
|
-
msg: error.message,
|
|
35
|
-
data: [],
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
return IWebResponse.success(data || []).to_compatible();
|
|
39
|
-
}
|
|
40
|
-
// Get operators by session id with additional filters
|
|
41
|
-
export async function server_get_operators_by_session_id_filtered(sessionId, action, timestampAfter) {
|
|
42
|
-
const supabase = await newServerClient();
|
|
43
|
-
let query = supabase
|
|
44
|
-
.from("operators")
|
|
45
|
-
.select("*")
|
|
46
|
-
.eq("session_id", sessionId);
|
|
47
|
-
// Apply optional filters
|
|
48
|
-
if (action) {
|
|
49
|
-
query = query.eq("action", action);
|
|
50
|
-
}
|
|
51
|
-
if (timestampAfter) {
|
|
52
|
-
query = query.gte("timestamp", timestampAfter);
|
|
53
|
-
}
|
|
54
|
-
const { data, error } = await query.order("timestamp", { ascending: false });
|
|
55
|
-
if (error) {
|
|
56
|
-
console.warn("Error fetching filtered operators by session id:", error.message);
|
|
57
|
-
return {
|
|
58
|
-
status: EnumWebResponse.ERROR,
|
|
59
|
-
msg: error.message,
|
|
60
|
-
data: [],
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
return IWebResponse.success(data || []).to_compatible();
|
|
64
|
-
}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
-
import { useInfiniteSessionsByHerd, useInfiniteEventsByHerd, useInfiniteArtifactsByHerd } from "./useInfiniteQuery";
|
|
3
|
-
import { IHerdModule } from "../types/herd_module";
|
|
4
|
-
import { ISessionSummary } from "../types/db";
|
|
5
|
-
interface UseHerdDataOptions {
|
|
6
|
-
sessionsLimit?: number;
|
|
7
|
-
eventsLimit?: number;
|
|
8
|
-
artifactsLimit?: number;
|
|
9
|
-
enableSessions?: boolean;
|
|
10
|
-
enableEvents?: boolean;
|
|
11
|
-
enableArtifacts?: boolean;
|
|
12
|
-
supabase: SupabaseClient;
|
|
13
|
-
}
|
|
14
|
-
interface UseHerdDataReturn {
|
|
15
|
-
herdModule: IHerdModule | undefined;
|
|
16
|
-
herd: IHerdModule["herd"] | undefined;
|
|
17
|
-
devices: IHerdModule["devices"];
|
|
18
|
-
zones: IHerdModule["zones"];
|
|
19
|
-
plans: IHerdModule["plans"];
|
|
20
|
-
layers: IHerdModule["layers"];
|
|
21
|
-
providers: IHerdModule["providers"];
|
|
22
|
-
labels: IHerdModule["labels"];
|
|
23
|
-
userRoles: IHerdModule["user_roles"];
|
|
24
|
-
sessionSummaries: ISessionSummary | null;
|
|
25
|
-
timestampLastRefreshed: number | null;
|
|
26
|
-
sessions: {
|
|
27
|
-
items: ReturnType<typeof useInfiniteSessionsByHerd>["items"];
|
|
28
|
-
isLoading: boolean;
|
|
29
|
-
isLoadingMore: boolean;
|
|
30
|
-
hasMore: boolean;
|
|
31
|
-
loadMore: () => void;
|
|
32
|
-
refetch: () => void;
|
|
33
|
-
error: any;
|
|
34
|
-
};
|
|
35
|
-
events: {
|
|
36
|
-
items: ReturnType<typeof useInfiniteEventsByHerd>["items"];
|
|
37
|
-
isLoading: boolean;
|
|
38
|
-
isLoadingMore: boolean;
|
|
39
|
-
hasMore: boolean;
|
|
40
|
-
loadMore: () => void;
|
|
41
|
-
refetch: () => void;
|
|
42
|
-
error: any;
|
|
43
|
-
};
|
|
44
|
-
artifacts: {
|
|
45
|
-
items: ReturnType<typeof useInfiniteArtifactsByHerd>["items"];
|
|
46
|
-
isLoading: boolean;
|
|
47
|
-
isLoadingMore: boolean;
|
|
48
|
-
hasMore: boolean;
|
|
49
|
-
loadMore: () => void;
|
|
50
|
-
refetch: () => void;
|
|
51
|
-
error: any;
|
|
52
|
-
};
|
|
53
|
-
isInitialLoading: boolean;
|
|
54
|
-
hasAnyError: boolean;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Comprehensive hook for accessing herd data from both global store and RTK Query
|
|
58
|
-
*
|
|
59
|
-
* @param herdId - The herd ID to fetch data for
|
|
60
|
-
* @param options - Configuration options for data fetching
|
|
61
|
-
* @returns Complete herd data with both structural and paginated content
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* ```tsx
|
|
65
|
-
* const {
|
|
66
|
-
* herd,
|
|
67
|
-
* devices,
|
|
68
|
-
* events,
|
|
69
|
-
* sessions,
|
|
70
|
-
* artifacts,
|
|
71
|
-
* sessionSummaries
|
|
72
|
-
* } = useHerdData(123, {
|
|
73
|
-
* supabase,
|
|
74
|
-
* eventsLimit: 20,
|
|
75
|
-
* enableSessions: true
|
|
76
|
-
* });
|
|
77
|
-
*
|
|
78
|
-
* // Use structural data from global store
|
|
79
|
-
* console.log(herd.name, devices.length);
|
|
80
|
-
*
|
|
81
|
-
* // Use paginated data from RTK Query
|
|
82
|
-
* console.log(events.items.length, events.hasMore);
|
|
83
|
-
* if (events.hasMore) {
|
|
84
|
-
* events.loadMore();
|
|
85
|
-
* }
|
|
86
|
-
*
|
|
87
|
-
* // Use session summaries
|
|
88
|
-
* console.log(sessionSummaries?.total_sessions, sessionSummaries?.total_distance_meters);
|
|
89
|
-
* ```
|
|
90
|
-
*/
|
|
91
|
-
export declare const useHerdData: (herdId: number, options: UseHerdDataOptions) => UseHerdDataReturn;
|
|
92
|
-
/**
|
|
93
|
-
* Hook for getting just the structural herd data from global store
|
|
94
|
-
* Useful when you only need basic herd info without paginated content
|
|
95
|
-
*/
|
|
96
|
-
export declare const useHerdStructure: (herdId: number) => {
|
|
97
|
-
herdModule: IHerdModule | undefined;
|
|
98
|
-
herd: {
|
|
99
|
-
created_by: string;
|
|
100
|
-
description: string;
|
|
101
|
-
earthranger_domain: string | null;
|
|
102
|
-
earthranger_token: string | null;
|
|
103
|
-
id: number;
|
|
104
|
-
inserted_at: string;
|
|
105
|
-
is_public: boolean;
|
|
106
|
-
slug: string;
|
|
107
|
-
video_publisher_token: string | null;
|
|
108
|
-
video_server_url: string | null;
|
|
109
|
-
video_subscriber_token: string | null;
|
|
110
|
-
} | undefined;
|
|
111
|
-
devices: import("../types/db").IDevice[];
|
|
112
|
-
zones: import("../types/db").IZoneWithActions[];
|
|
113
|
-
plans: {
|
|
114
|
-
herd_id: number;
|
|
115
|
-
id: number;
|
|
116
|
-
inserted_at: string | null;
|
|
117
|
-
instructions: string;
|
|
118
|
-
name: string;
|
|
119
|
-
plan_type: import("..").Database["public"]["Enums"]["plan_type"];
|
|
120
|
-
}[];
|
|
121
|
-
layers: {
|
|
122
|
-
created_at: string;
|
|
123
|
-
features: import("..").Json;
|
|
124
|
-
herd_id: number;
|
|
125
|
-
id: number;
|
|
126
|
-
}[];
|
|
127
|
-
providers: {
|
|
128
|
-
created_at: string;
|
|
129
|
-
herd_id: number;
|
|
130
|
-
id: number;
|
|
131
|
-
key: string | null;
|
|
132
|
-
source: string;
|
|
133
|
-
type: string;
|
|
134
|
-
}[];
|
|
135
|
-
labels: string[];
|
|
136
|
-
userRoles: import("../types/db").IUserAndRole[] | null;
|
|
137
|
-
sessionSummaries: ISessionSummary | null;
|
|
138
|
-
timestampLastRefreshed: number | null;
|
|
139
|
-
};
|
|
140
|
-
/**
|
|
141
|
-
* Hook for getting all herd structures (without paginated data)
|
|
142
|
-
* Useful for dashboard/overview components
|
|
143
|
-
*/
|
|
144
|
-
export declare const useAllHerds: () => {
|
|
145
|
-
herdModules: IHerdModule[];
|
|
146
|
-
herds: {
|
|
147
|
-
created_by: string;
|
|
148
|
-
description: string;
|
|
149
|
-
earthranger_domain: string | null;
|
|
150
|
-
earthranger_token: string | null;
|
|
151
|
-
id: number;
|
|
152
|
-
inserted_at: string;
|
|
153
|
-
is_public: boolean;
|
|
154
|
-
slug: string;
|
|
155
|
-
video_publisher_token: string | null;
|
|
156
|
-
video_server_url: string | null;
|
|
157
|
-
video_subscriber_token: string | null;
|
|
158
|
-
}[];
|
|
159
|
-
totalHerds: number;
|
|
160
|
-
totalDevices: number;
|
|
161
|
-
sessionSummaries: {
|
|
162
|
-
herdId: number;
|
|
163
|
-
herdSlug: string;
|
|
164
|
-
summaries: ISessionSummary | null;
|
|
165
|
-
}[];
|
|
166
|
-
};
|
|
167
|
-
export {};
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { useSelector } from "react-redux";
|
|
2
|
-
import { useInfiniteSessionsByHerd, useInfiniteEventsByHerd, useInfiniteArtifactsByHerd, } from "./useInfiniteQuery";
|
|
3
|
-
/**
|
|
4
|
-
* Comprehensive hook for accessing herd data from both global store and RTK Query
|
|
5
|
-
*
|
|
6
|
-
* @param herdId - The herd ID to fetch data for
|
|
7
|
-
* @param options - Configuration options for data fetching
|
|
8
|
-
* @returns Complete herd data with both structural and paginated content
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```tsx
|
|
12
|
-
* const {
|
|
13
|
-
* herd,
|
|
14
|
-
* devices,
|
|
15
|
-
* events,
|
|
16
|
-
* sessions,
|
|
17
|
-
* artifacts,
|
|
18
|
-
* sessionSummaries
|
|
19
|
-
* } = useHerdData(123, {
|
|
20
|
-
* supabase,
|
|
21
|
-
* eventsLimit: 20,
|
|
22
|
-
* enableSessions: true
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
* // Use structural data from global store
|
|
26
|
-
* console.log(herd.name, devices.length);
|
|
27
|
-
*
|
|
28
|
-
* // Use paginated data from RTK Query
|
|
29
|
-
* console.log(events.items.length, events.hasMore);
|
|
30
|
-
* if (events.hasMore) {
|
|
31
|
-
* events.loadMore();
|
|
32
|
-
* }
|
|
33
|
-
*
|
|
34
|
-
* // Use session summaries
|
|
35
|
-
* console.log(sessionSummaries?.total_sessions, sessionSummaries?.total_distance_meters);
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export const useHerdData = (herdId, options) => {
|
|
39
|
-
const { sessionsLimit = 20, eventsLimit = 20, artifactsLimit = 20, enableSessions = true, enableEvents = true, enableArtifacts = true, supabase, } = options;
|
|
40
|
-
// Get structural data from global store
|
|
41
|
-
const herdModule = useSelector((state) => state.scout.herd_modules.find((hm) => hm.herd.id === herdId));
|
|
42
|
-
// RTK Query infinite data hooks
|
|
43
|
-
const sessionsQuery = useInfiniteSessionsByHerd(herdId, {
|
|
44
|
-
limit: sessionsLimit,
|
|
45
|
-
enabled: enableSessions,
|
|
46
|
-
supabase,
|
|
47
|
-
});
|
|
48
|
-
const eventsQuery = useInfiniteEventsByHerd(herdId, {
|
|
49
|
-
limit: eventsLimit,
|
|
50
|
-
enabled: enableEvents,
|
|
51
|
-
supabase,
|
|
52
|
-
});
|
|
53
|
-
const artifactsQuery = useInfiniteArtifactsByHerd(herdId, {
|
|
54
|
-
limit: artifactsLimit,
|
|
55
|
-
enabled: enableArtifacts,
|
|
56
|
-
supabase,
|
|
57
|
-
});
|
|
58
|
-
// Overall loading state - true if any enabled query is loading for the first time
|
|
59
|
-
const isInitialLoading = (enableSessions &&
|
|
60
|
-
sessionsQuery.isLoading &&
|
|
61
|
-
sessionsQuery.items.length === 0) ||
|
|
62
|
-
(enableEvents && eventsQuery.isLoading && eventsQuery.items.length === 0) ||
|
|
63
|
-
(enableArtifacts &&
|
|
64
|
-
artifactsQuery.isLoading &&
|
|
65
|
-
artifactsQuery.items.length === 0);
|
|
66
|
-
// Overall error state - true if any enabled query has an error
|
|
67
|
-
const hasAnyError = (enableSessions && !!sessionsQuery.error) ||
|
|
68
|
-
(enableEvents && !!eventsQuery.error) ||
|
|
69
|
-
(enableArtifacts && !!artifactsQuery.error);
|
|
70
|
-
return {
|
|
71
|
-
// Global store structural data
|
|
72
|
-
herdModule,
|
|
73
|
-
herd: herdModule?.herd,
|
|
74
|
-
devices: herdModule?.devices || [],
|
|
75
|
-
zones: herdModule?.zones || [],
|
|
76
|
-
plans: herdModule?.plans || [],
|
|
77
|
-
layers: herdModule?.layers || [],
|
|
78
|
-
providers: herdModule?.providers || [],
|
|
79
|
-
labels: herdModule?.labels || [],
|
|
80
|
-
userRoles: herdModule?.user_roles || null,
|
|
81
|
-
sessionSummaries: herdModule?.session_summaries || null,
|
|
82
|
-
timestampLastRefreshed: herdModule?.timestamp_last_refreshed || null,
|
|
83
|
-
// RTK Query paginated data
|
|
84
|
-
sessions: {
|
|
85
|
-
items: sessionsQuery.items,
|
|
86
|
-
isLoading: sessionsQuery.isLoading,
|
|
87
|
-
isLoadingMore: sessionsQuery.isLoadingMore,
|
|
88
|
-
hasMore: sessionsQuery.hasMore,
|
|
89
|
-
loadMore: sessionsQuery.loadMore,
|
|
90
|
-
refetch: sessionsQuery.refetch,
|
|
91
|
-
error: sessionsQuery.error,
|
|
92
|
-
},
|
|
93
|
-
events: {
|
|
94
|
-
items: eventsQuery.items,
|
|
95
|
-
isLoading: eventsQuery.isLoading,
|
|
96
|
-
isLoadingMore: eventsQuery.isLoadingMore,
|
|
97
|
-
hasMore: eventsQuery.hasMore,
|
|
98
|
-
loadMore: eventsQuery.loadMore,
|
|
99
|
-
refetch: eventsQuery.refetch,
|
|
100
|
-
error: eventsQuery.error,
|
|
101
|
-
},
|
|
102
|
-
artifacts: {
|
|
103
|
-
items: artifactsQuery.items,
|
|
104
|
-
isLoading: artifactsQuery.isLoading,
|
|
105
|
-
isLoadingMore: artifactsQuery.isLoadingMore,
|
|
106
|
-
hasMore: artifactsQuery.hasMore,
|
|
107
|
-
loadMore: artifactsQuery.loadMore,
|
|
108
|
-
refetch: artifactsQuery.refetch,
|
|
109
|
-
error: artifactsQuery.error,
|
|
110
|
-
},
|
|
111
|
-
// Overall states
|
|
112
|
-
isInitialLoading,
|
|
113
|
-
hasAnyError,
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
/**
|
|
117
|
-
* Hook for getting just the structural herd data from global store
|
|
118
|
-
* Useful when you only need basic herd info without paginated content
|
|
119
|
-
*/
|
|
120
|
-
export const useHerdStructure = (herdId) => {
|
|
121
|
-
const herdModule = useSelector((state) => state.scout.herd_modules.find((hm) => hm.herd.id === herdId));
|
|
122
|
-
return {
|
|
123
|
-
herdModule,
|
|
124
|
-
herd: herdModule?.herd,
|
|
125
|
-
devices: herdModule?.devices || [],
|
|
126
|
-
zones: herdModule?.zones || [],
|
|
127
|
-
plans: herdModule?.plans || [],
|
|
128
|
-
layers: herdModule?.layers || [],
|
|
129
|
-
providers: herdModule?.providers || [],
|
|
130
|
-
labels: herdModule?.labels || [],
|
|
131
|
-
userRoles: herdModule?.user_roles || null,
|
|
132
|
-
sessionSummaries: herdModule?.session_summaries || null,
|
|
133
|
-
timestampLastRefreshed: herdModule?.timestamp_last_refreshed || null,
|
|
134
|
-
};
|
|
135
|
-
};
|
|
136
|
-
/**
|
|
137
|
-
* Hook for getting all herd structures (without paginated data)
|
|
138
|
-
* Useful for dashboard/overview components
|
|
139
|
-
*/
|
|
140
|
-
export const useAllHerds = () => {
|
|
141
|
-
const herdModules = useSelector((state) => state.scout.herd_modules);
|
|
142
|
-
return {
|
|
143
|
-
herdModules,
|
|
144
|
-
herds: herdModules.map((hm) => hm.herd),
|
|
145
|
-
totalHerds: herdModules.length,
|
|
146
|
-
totalDevices: herdModules.reduce((sum, hm) => sum + hm.devices.length, 0),
|
|
147
|
-
sessionSummaries: herdModules.map((hm) => ({
|
|
148
|
-
herdId: hm.herd.id,
|
|
149
|
-
herdSlug: hm.herd.slug,
|
|
150
|
-
summaries: hm.session_summaries,
|
|
151
|
-
})),
|
|
152
|
-
};
|
|
153
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
-
import { Database } from "../types/supabase";
|
|
3
|
-
/**
|
|
4
|
-
* Creates one browser Supabase client (reads NEXT_PUBLIC_* env each call).
|
|
5
|
-
* Prefer {@link useScoutBrowserClient} in components so creation runs once via lazy state init.
|
|
6
|
-
*/
|
|
7
|
-
export declare function createScoutBrowserClient(): SupabaseClient<Database>;
|
|
8
|
-
/**
|
|
9
|
-
* One browser Supabase client per component instance, created on first render (lazy state initializer).
|
|
10
|
-
*/
|
|
11
|
-
export declare function useScoutBrowserClient(): SupabaseClient<Database>;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import { createBrowserClient } from "@supabase/ssr";
|
|
4
|
-
/**
|
|
5
|
-
* Creates one browser Supabase client (reads NEXT_PUBLIC_* env each call).
|
|
6
|
-
* Prefer {@link useScoutBrowserClient} in components so creation runs once via lazy state init.
|
|
7
|
-
*/
|
|
8
|
-
export function createScoutBrowserClient() {
|
|
9
|
-
return createBrowserClient(process.env.NEXT_PUBLIC_SUPABASE_URL || "", process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "");
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* One browser Supabase client per component instance, created on first render (lazy state initializer).
|
|
13
|
-
*/
|
|
14
|
-
export function useScoutBrowserClient() {
|
|
15
|
-
const [client] = useState(createScoutBrowserClient);
|
|
16
|
-
return client;
|
|
17
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useAppDispatch } from "../store/hooks";
|
|
3
|
-
import { useEffect, useRef } from "react";
|
|
4
|
-
import { addDevice, addPlan, addTag, addSessionToStore, deleteDevice, deletePlan, deleteSessionFromStore, deleteTag, updateDevice, updatePlan, updateSessionInStore, updateTag, } from "../store/scout";
|
|
5
|
-
export function useScoutDbListener(scoutSupabase) {
|
|
6
|
-
const supabase = useRef(null);
|
|
7
|
-
const channels = useRef([]);
|
|
8
|
-
const dispatch = useAppDispatch();
|
|
9
|
-
function handleTagInserts(payload) {
|
|
10
|
-
console.log("[DB Listener] Tag INSERT received:", payload.new);
|
|
11
|
-
dispatch(addTag(payload.new));
|
|
12
|
-
}
|
|
13
|
-
function handleTagDeletes(payload) {
|
|
14
|
-
console.log("[DB Listener] Tag DELETE received:", payload.old);
|
|
15
|
-
if (!payload.old || !payload.old.id) {
|
|
16
|
-
console.error("[DB Listener] Tag DELETE - Invalid payload, missing tag data");
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
dispatch(deleteTag(payload.old));
|
|
20
|
-
}
|
|
21
|
-
function handleTagUpdates(payload) {
|
|
22
|
-
console.log("[DB Listener] Tag UPDATE received:", payload.new);
|
|
23
|
-
dispatch(updateTag(payload.new));
|
|
24
|
-
}
|
|
25
|
-
function handleDeviceInserts(payload) {
|
|
26
|
-
console.log("[DB Listener] Device INSERT received:", payload.new);
|
|
27
|
-
dispatch(addDevice(payload.new));
|
|
28
|
-
}
|
|
29
|
-
function handleDeviceDeletes(payload) {
|
|
30
|
-
console.log("[DB Listener] Device DELETE received:", payload.old);
|
|
31
|
-
dispatch(deleteDevice(payload.old));
|
|
32
|
-
}
|
|
33
|
-
function handleDeviceUpdates(payload) {
|
|
34
|
-
console.log("[DB Listener] Device UPDATE received:", payload.new);
|
|
35
|
-
dispatch(updateDevice(payload.new));
|
|
36
|
-
}
|
|
37
|
-
function handlePlanInserts(payload) {
|
|
38
|
-
console.log("[DB Listener] Plan INSERT received:", payload.new);
|
|
39
|
-
dispatch(addPlan(payload.new));
|
|
40
|
-
}
|
|
41
|
-
function handlePlanDeletes(payload) {
|
|
42
|
-
console.log("[DB Listener] Plan DELETE received:", payload.old);
|
|
43
|
-
dispatch(deletePlan(payload.old));
|
|
44
|
-
}
|
|
45
|
-
function handlePlanUpdates(payload) {
|
|
46
|
-
console.log("[DB Listener] Plan UPDATE received:", payload.new);
|
|
47
|
-
dispatch(updatePlan(payload.new));
|
|
48
|
-
}
|
|
49
|
-
function handleSessionInserts(payload) {
|
|
50
|
-
console.log("[DB Listener] Session INSERT received:", payload.new);
|
|
51
|
-
dispatch(addSessionToStore(payload.new));
|
|
52
|
-
}
|
|
53
|
-
function handleSessionDeletes(payload) {
|
|
54
|
-
console.log("[DB Listener] Session DELETE received:", payload.old);
|
|
55
|
-
if (!payload.old || !payload.old.id) {
|
|
56
|
-
console.error("[DB Listener] Session DELETE - Invalid payload, missing session data");
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
dispatch(deleteSessionFromStore(payload.old));
|
|
60
|
-
}
|
|
61
|
-
function handleSessionUpdates(payload) {
|
|
62
|
-
console.log("[DB Listener] Session UPDATE received:", payload.new);
|
|
63
|
-
dispatch(updateSessionInStore(payload.new));
|
|
64
|
-
}
|
|
65
|
-
function handleConnectivityInserts(payload) {
|
|
66
|
-
console.log("[DB Listener] Connectivity INSERT received:", payload.new);
|
|
67
|
-
}
|
|
68
|
-
function handleConnectivityDeletes(payload) {
|
|
69
|
-
console.log("[DB Listener] Connectivity DELETE received:", payload.old);
|
|
70
|
-
}
|
|
71
|
-
function handleConnectivityUpdates(payload) {
|
|
72
|
-
console.log("[DB Listener] Connectivity UPDATE received:", payload.new);
|
|
73
|
-
}
|
|
74
|
-
// Clean up all channels
|
|
75
|
-
const cleanupChannels = () => {
|
|
76
|
-
channels.current.forEach((channel) => {
|
|
77
|
-
if (channel) {
|
|
78
|
-
scoutSupabase.removeChannel(channel);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
channels.current = [];
|
|
82
|
-
};
|
|
83
|
-
// Setup channel with event handlers
|
|
84
|
-
const setupChannel = () => {
|
|
85
|
-
if (!scoutSupabase)
|
|
86
|
-
return null;
|
|
87
|
-
const channelId = `scout_realtime_${Date.now()}_${Math.random()
|
|
88
|
-
.toString(36)
|
|
89
|
-
.substr(2, 9)}`;
|
|
90
|
-
const mainChannel = scoutSupabase.channel(channelId);
|
|
91
|
-
console.log(`[DB Listener] Creating channel: ${channelId}`);
|
|
92
|
-
// Subscribe to all events
|
|
93
|
-
mainChannel
|
|
94
|
-
.on("postgres_changes", { event: "INSERT", schema: "public", table: "plans" }, handlePlanInserts)
|
|
95
|
-
.on("postgres_changes", { event: "DELETE", schema: "public", table: "plans" }, handlePlanDeletes)
|
|
96
|
-
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "plans" }, handlePlanUpdates)
|
|
97
|
-
.on("postgres_changes", { event: "INSERT", schema: "public", table: "devices" }, handleDeviceInserts)
|
|
98
|
-
.on("postgres_changes", { event: "DELETE", schema: "public", table: "devices" }, handleDeviceDeletes)
|
|
99
|
-
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "devices" }, handleDeviceUpdates)
|
|
100
|
-
.on("postgres_changes", { event: "INSERT", schema: "public", table: "tags" }, handleTagInserts)
|
|
101
|
-
.on("postgres_changes", { event: "DELETE", schema: "public", table: "tags" }, handleTagDeletes)
|
|
102
|
-
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "tags" }, handleTagUpdates)
|
|
103
|
-
.on("postgres_changes", { event: "INSERT", schema: "public", table: "connectivity" }, handleConnectivityInserts)
|
|
104
|
-
.on("postgres_changes", { event: "DELETE", schema: "public", table: "connectivity" }, handleConnectivityDeletes)
|
|
105
|
-
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "connectivity" }, handleConnectivityUpdates)
|
|
106
|
-
.on("postgres_changes", { event: "INSERT", schema: "public", table: "sessions" }, handleSessionInserts)
|
|
107
|
-
.on("postgres_changes", { event: "DELETE", schema: "public", table: "sessions" }, handleSessionDeletes)
|
|
108
|
-
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "sessions" }, handleSessionUpdates)
|
|
109
|
-
.subscribe((status) => {
|
|
110
|
-
console.log("[DB Listener] Subscription status:", status);
|
|
111
|
-
if (status === "SUBSCRIBED") {
|
|
112
|
-
console.log("[DB Listener] ✅ Successfully subscribed to real-time updates");
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
return mainChannel;
|
|
116
|
-
};
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
if (!scoutSupabase) {
|
|
119
|
-
console.error("[DB Listener] No Supabase client available");
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
supabase.current = scoutSupabase;
|
|
123
|
-
// Initial channel setup
|
|
124
|
-
const mainChannel = setupChannel();
|
|
125
|
-
if (mainChannel) {
|
|
126
|
-
channels.current.push(mainChannel);
|
|
127
|
-
}
|
|
128
|
-
// Cleanup function
|
|
129
|
-
return () => {
|
|
130
|
-
console.log("[DB Listener] 🧹 Cleaning up channels");
|
|
131
|
-
cleanupChannels();
|
|
132
|
-
};
|
|
133
|
-
}, [scoutSupabase, dispatch]);
|
|
134
|
-
}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useAppDispatch } from "../store/hooks";
|
|
3
|
-
import { useSelector } from "react-redux";
|
|
4
|
-
import { useEffect, useRef, useCallback, useMemo } from "react";
|
|
5
|
-
import { setActiveHerdGpsTrackersConnectivity } from "../store/scout";
|
|
6
|
-
import { server_get_connectivity_by_device_id } from "../helpers/connectivity";
|
|
7
|
-
import { EnumWebResponse } from "../types/requests";
|
|
8
|
-
import { getHoursAgoTimestamp } from "../helpers/time";
|
|
9
|
-
export function useScoutRealtimeConnectivity(scoutSupabase) {
|
|
10
|
-
const channels = useRef([]);
|
|
11
|
-
const dispatch = useAppDispatch();
|
|
12
|
-
const activeHerdId = useSelector((state) => state.scout.active_herd_id);
|
|
13
|
-
const connectivity = useSelector((state) => state.scout.active_herd_gps_trackers_connectivity);
|
|
14
|
-
const herdModules = useSelector((state) => state.scout.herd_modules);
|
|
15
|
-
// Create stable reference for GPS device IDs to prevent unnecessary refetching
|
|
16
|
-
const gpsDeviceIds = useMemo(() => {
|
|
17
|
-
if (!activeHerdId)
|
|
18
|
-
return "";
|
|
19
|
-
const activeHerdModule = herdModules.find((hm) => hm.herd.id.toString() === activeHerdId);
|
|
20
|
-
if (!activeHerdModule)
|
|
21
|
-
return "";
|
|
22
|
-
const gpsDevices = activeHerdModule.devices.filter((device) => device.device_type &&
|
|
23
|
-
["gps_tracker", "gps_tracker_vehicle", "gps_tracker_person"].includes(device.device_type));
|
|
24
|
-
return gpsDevices
|
|
25
|
-
.map((d) => d.id)
|
|
26
|
-
.filter(Boolean)
|
|
27
|
-
.sort()
|
|
28
|
-
.join(",");
|
|
29
|
-
}, [activeHerdId, herdModules]);
|
|
30
|
-
// Handle connectivity broadcasts
|
|
31
|
-
const handleConnectivityBroadcast = useCallback((payload) => {
|
|
32
|
-
const { event, payload: data } = payload;
|
|
33
|
-
const connectivityData = data.record || data.old_record;
|
|
34
|
-
// Only process GPS tracker data (no session_id)
|
|
35
|
-
if (!connectivityData?.device_id || connectivityData.session_id) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const deviceId = connectivityData.device_id;
|
|
39
|
-
const updatedConnectivity = { ...connectivity };
|
|
40
|
-
switch (data.operation) {
|
|
41
|
-
case "INSERT":
|
|
42
|
-
console.log(`[CONNECTIVITY] INSERT for ${deviceId}, ${JSON.stringify(connectivityData)}`);
|
|
43
|
-
if (!updatedConnectivity[deviceId]) {
|
|
44
|
-
updatedConnectivity[deviceId] = {
|
|
45
|
-
most_recent: connectivityData,
|
|
46
|
-
history: [],
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
const newHistory = [
|
|
51
|
-
updatedConnectivity[deviceId].most_recent,
|
|
52
|
-
...updatedConnectivity[deviceId].history,
|
|
53
|
-
].slice(0, 99);
|
|
54
|
-
updatedConnectivity[deviceId] = {
|
|
55
|
-
most_recent: connectivityData,
|
|
56
|
-
history: newHistory,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
break;
|
|
60
|
-
case "UPDATE":
|
|
61
|
-
if (updatedConnectivity[deviceId]) {
|
|
62
|
-
if (updatedConnectivity[deviceId].most_recent.id ===
|
|
63
|
-
connectivityData.id) {
|
|
64
|
-
updatedConnectivity[deviceId] = {
|
|
65
|
-
...updatedConnectivity[deviceId],
|
|
66
|
-
most_recent: connectivityData,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
const historyIndex = updatedConnectivity[deviceId].history.findIndex((c) => c.id === connectivityData.id);
|
|
71
|
-
if (historyIndex >= 0) {
|
|
72
|
-
const newHistory = [...updatedConnectivity[deviceId].history];
|
|
73
|
-
newHistory[historyIndex] = connectivityData;
|
|
74
|
-
updatedConnectivity[deviceId] = {
|
|
75
|
-
...updatedConnectivity[deviceId],
|
|
76
|
-
history: newHistory,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
break;
|
|
82
|
-
case "DELETE":
|
|
83
|
-
if (updatedConnectivity[deviceId]) {
|
|
84
|
-
if (updatedConnectivity[deviceId].most_recent.id ===
|
|
85
|
-
connectivityData.id) {
|
|
86
|
-
if (updatedConnectivity[deviceId].history.length === 0) {
|
|
87
|
-
delete updatedConnectivity[deviceId];
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
updatedConnectivity[deviceId] = {
|
|
91
|
-
most_recent: updatedConnectivity[deviceId].history[0],
|
|
92
|
-
history: updatedConnectivity[deviceId].history.slice(1),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
updatedConnectivity[deviceId] = {
|
|
98
|
-
...updatedConnectivity[deviceId],
|
|
99
|
-
history: updatedConnectivity[deviceId].history.filter((c) => c.id !== connectivityData.id),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
dispatch(setActiveHerdGpsTrackersConnectivity(updatedConnectivity));
|
|
106
|
-
}, [connectivity, dispatch]);
|
|
107
|
-
// Fetch initial connectivity data
|
|
108
|
-
const fetchInitialData = useCallback(async () => {
|
|
109
|
-
if (!gpsDeviceIds)
|
|
110
|
-
return;
|
|
111
|
-
const deviceIds = gpsDeviceIds.split(",").filter(Boolean).map(Number);
|
|
112
|
-
if (deviceIds.length === 0) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const timestampFilter = getHoursAgoTimestamp(1);
|
|
116
|
-
const connectivityData = {};
|
|
117
|
-
await Promise.all(deviceIds.map(async (deviceId) => {
|
|
118
|
-
try {
|
|
119
|
-
const response = await server_get_connectivity_by_device_id(deviceId, timestampFilter);
|
|
120
|
-
if (response.status === EnumWebResponse.SUCCESS && response.data) {
|
|
121
|
-
const trackerData = response.data.filter((conn) => !conn.session_id);
|
|
122
|
-
if (trackerData.length > 0) {
|
|
123
|
-
const sortedData = trackerData
|
|
124
|
-
.sort((a, b) => new Date(b.timestamp_start || 0).getTime() -
|
|
125
|
-
new Date(a.timestamp_start || 0).getTime())
|
|
126
|
-
.slice(0, 100);
|
|
127
|
-
connectivityData[deviceId] = {
|
|
128
|
-
most_recent: sortedData[0],
|
|
129
|
-
history: sortedData.slice(1), // Exclude the most recent item
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
// Silent error handling
|
|
136
|
-
}
|
|
137
|
-
}));
|
|
138
|
-
dispatch(setActiveHerdGpsTrackersConnectivity(connectivityData));
|
|
139
|
-
}, [gpsDeviceIds, dispatch]);
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
if (!scoutSupabase || gpsDeviceIds === "")
|
|
142
|
-
return;
|
|
143
|
-
// Clean up existing channels
|
|
144
|
-
channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
|
|
145
|
-
channels.current = [];
|
|
146
|
-
// Create connectivity channel
|
|
147
|
-
const channel = scoutSupabase
|
|
148
|
-
.channel(`${activeHerdId}-connectivity`, { config: { private: true } })
|
|
149
|
-
.on("broadcast", { event: "*" }, handleConnectivityBroadcast)
|
|
150
|
-
.subscribe();
|
|
151
|
-
channels.current.push(channel);
|
|
152
|
-
// Fetch initial data
|
|
153
|
-
fetchInitialData();
|
|
154
|
-
return () => {
|
|
155
|
-
channels.current.forEach((ch) => scoutSupabase.removeChannel(ch));
|
|
156
|
-
channels.current = [];
|
|
157
|
-
};
|
|
158
|
-
}, [scoutSupabase, gpsDeviceIds, activeHerdId, handleConnectivityBroadcast]);
|
|
159
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useAppDispatch } from "../store/hooks";
|
|
3
|
-
import { useSelector } from "react-redux";
|
|
4
|
-
import { useEffect, useRef, useCallback } from "react";
|
|
5
|
-
import { addDevice, deleteDevice, updateDevice } from "../store/scout";
|
|
6
|
-
export function useScoutRealtimeDevices(scoutSupabase) {
|
|
7
|
-
const channels = useRef([]);
|
|
8
|
-
const dispatch = useAppDispatch();
|
|
9
|
-
const activeHerdId = useSelector((state) => state.scout.active_herd_id);
|
|
10
|
-
// Device broadcast handler
|
|
11
|
-
const handleDeviceBroadcast = useCallback((payload) => {
|
|
12
|
-
console.log("[Devices] Broadcast received:", payload.payload.operation);
|
|
13
|
-
const data = payload.payload;
|
|
14
|
-
switch (data.operation) {
|
|
15
|
-
case "INSERT":
|
|
16
|
-
if (data.record)
|
|
17
|
-
dispatch(addDevice(data.record));
|
|
18
|
-
break;
|
|
19
|
-
case "UPDATE":
|
|
20
|
-
if (data.record)
|
|
21
|
-
dispatch(updateDevice(data.record));
|
|
22
|
-
break;
|
|
23
|
-
case "DELETE":
|
|
24
|
-
if (data.old_record)
|
|
25
|
-
dispatch(deleteDevice(data.old_record));
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
}, [dispatch]);
|
|
29
|
-
const cleanupChannels = () => {
|
|
30
|
-
channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
|
|
31
|
-
channels.current = [];
|
|
32
|
-
};
|
|
33
|
-
const createDevicesChannel = (herdId) => {
|
|
34
|
-
return scoutSupabase
|
|
35
|
-
.channel(`${herdId}-devices`, { config: { private: true } })
|
|
36
|
-
.on("broadcast", { event: "*" }, handleDeviceBroadcast)
|
|
37
|
-
.subscribe((status) => {
|
|
38
|
-
if (status === "SUBSCRIBED") {
|
|
39
|
-
console.log(`[Devices] ✅ Connected to herd ${herdId}`);
|
|
40
|
-
}
|
|
41
|
-
else if (status === "CHANNEL_ERROR") {
|
|
42
|
-
console.warn(`[Devices] 🟡 Failed to connect to herd ${herdId}`);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
cleanupChannels();
|
|
48
|
-
// Create devices channel for active herd
|
|
49
|
-
if (activeHerdId) {
|
|
50
|
-
const channel = createDevicesChannel(activeHerdId);
|
|
51
|
-
channels.current.push(channel);
|
|
52
|
-
}
|
|
53
|
-
return cleanupChannels;
|
|
54
|
-
}, [activeHerdId]);
|
|
55
|
-
}
|
package/dist/types/data.d.ts
DELETED
package/dist/types/data.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|