@optifye/dashboard-core 4.2.6 → 4.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +82 -21
- package/dist/index.d.ts +82 -21
- package/dist/index.js +2235 -2039
- package/dist/index.mjs +2232 -2039
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2,12 +2,12 @@ import * as React14 from 'react';
|
|
|
2
2
|
import React14__default, { createContext, useRef, useCallback, useState, useMemo, useEffect, memo, useContext, useLayoutEffect, useId, Children, isValidElement, useInsertionEffect, forwardRef, Fragment as Fragment$1, createElement, Component } from 'react';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import { useRouter } from 'next/router';
|
|
5
|
-
import { subDays, format, parseISO, isValid, isFuture, isToday } from 'date-fns';
|
|
6
5
|
import { toZonedTime, formatInTimeZone } from 'date-fns-tz';
|
|
7
|
-
import {
|
|
6
|
+
import { subDays, format, parseISO, isValid, isFuture, isToday } from 'date-fns';
|
|
8
7
|
import mixpanel from 'mixpanel-browser';
|
|
9
|
-
import
|
|
8
|
+
import { REALTIME_SUBSCRIBE_STATES, createClient } from '@supabase/supabase-js';
|
|
10
9
|
import Hls2 from 'hls.js';
|
|
10
|
+
import useSWR from 'swr';
|
|
11
11
|
import { noop, warning, invariant, progress, secondsToMilliseconds, millisecondsToSeconds, memo as memo$1 } from 'motion-utils';
|
|
12
12
|
import { getValueTransition, hover, press, isPrimaryPointer, GroupPlaybackControls, setDragLock, supportsLinearEasing, attachTimeline, isGenerator, calcGeneratorDuration, isWaapiSupportedEasing, mapEasingToNativeEasing, maxGeneratorDuration, generateLinearEasing, isBezierDefinition } from 'motion-dom';
|
|
13
13
|
import { ResponsiveContainer, BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, ReferenceLine, Bar, Cell, LabelList, PieChart, Pie, Legend, LineChart as LineChart$1, Line } from 'recharts';
|
|
@@ -125,6 +125,12 @@ var DEFAULT_ANALYTICS_CONFIG = {
|
|
|
125
125
|
var DEFAULT_AUTH_CONFIG = {
|
|
126
126
|
// Defaults related to auth providers, redirects etc.
|
|
127
127
|
};
|
|
128
|
+
var DEFAULT_VIDEO_CONFIG = {
|
|
129
|
+
canvasConfig: {
|
|
130
|
+
fps: 30,
|
|
131
|
+
useRAF: true
|
|
132
|
+
}
|
|
133
|
+
};
|
|
128
134
|
var LINE_1_UUID = "910a224b-0abc-459a-babb-4c899824cfe7";
|
|
129
135
|
var DEFAULT_CONFIG = {
|
|
130
136
|
apiBaseUrl: void 0,
|
|
@@ -138,7 +144,8 @@ var DEFAULT_CONFIG = {
|
|
|
138
144
|
// Add entity config here
|
|
139
145
|
shiftConfig: DEFAULT_SHIFT_CONFIG,
|
|
140
146
|
workspaceConfig: DEFAULT_WORKSPACE_CONFIG,
|
|
141
|
-
endpoints: DEFAULT_ENDPOINTS_CONFIG
|
|
147
|
+
endpoints: DEFAULT_ENDPOINTS_CONFIG,
|
|
148
|
+
videoConfig: DEFAULT_VIDEO_CONFIG
|
|
142
149
|
};
|
|
143
150
|
|
|
144
151
|
// src/lib/utils/config.ts
|
|
@@ -270,172 +277,9 @@ function useCustomConfig() {
|
|
|
270
277
|
const { customConfig } = useDashboardConfig();
|
|
271
278
|
return customConfig ?? {};
|
|
272
279
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
loading: true,
|
|
277
|
-
error: null,
|
|
278
|
-
signOut: async () => {
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
var useAuth = () => useContext(AuthContext);
|
|
282
|
-
var AuthProvider = ({ children }) => {
|
|
283
|
-
const supabase = useSupabase();
|
|
284
|
-
const { authConfig } = useDashboardConfig();
|
|
285
|
-
const [session, setSession] = useState(null);
|
|
286
|
-
const [user, setUser] = useState(null);
|
|
287
|
-
const [loading, setLoading] = useState(true);
|
|
288
|
-
const [error, setError] = useState(null);
|
|
289
|
-
const router = useRouter();
|
|
290
|
-
const userProfileTable = authConfig?.userProfileTable;
|
|
291
|
-
const roleColumn = authConfig?.roleColumn || "role";
|
|
292
|
-
const fetchUserDetails = useCallback(async (supabaseUser) => {
|
|
293
|
-
if (!supabaseUser) return null;
|
|
294
|
-
const basicUser = {
|
|
295
|
-
id: supabaseUser.id,
|
|
296
|
-
email: supabaseUser.email
|
|
297
|
-
};
|
|
298
|
-
if (!userProfileTable || !supabase) return basicUser;
|
|
299
|
-
try {
|
|
300
|
-
const timeoutPromise = new Promise(
|
|
301
|
-
(_, reject) => setTimeout(() => reject(new Error("Profile fetch timeout")), 5e3)
|
|
302
|
-
);
|
|
303
|
-
const fetchPromise = supabase.from(userProfileTable).select(roleColumn).eq("id", supabaseUser.id).single();
|
|
304
|
-
const { data: profile, error: profileError } = await Promise.race([
|
|
305
|
-
fetchPromise,
|
|
306
|
-
timeoutPromise
|
|
307
|
-
]);
|
|
308
|
-
if (profileError) {
|
|
309
|
-
if (profileError.message.includes("does not exist") || profileError.message.includes("No rows found") || profileError.code === "PGRST116") {
|
|
310
|
-
console.log("User profile table not found or user not in table, using basic auth info");
|
|
311
|
-
return basicUser;
|
|
312
|
-
}
|
|
313
|
-
console.error("Error fetching user profile:", profileError);
|
|
314
|
-
return basicUser;
|
|
315
|
-
}
|
|
316
|
-
const roleValue = profile ? profile[roleColumn] : void 0;
|
|
317
|
-
return { ...basicUser, role: roleValue };
|
|
318
|
-
} catch (err) {
|
|
319
|
-
console.error("Error fetching user profile:", err);
|
|
320
|
-
return basicUser;
|
|
321
|
-
}
|
|
322
|
-
}, [supabase, userProfileTable, roleColumn]);
|
|
323
|
-
useEffect(() => {
|
|
324
|
-
if (!supabase) return;
|
|
325
|
-
let mounted = true;
|
|
326
|
-
const safetyTimeout = setTimeout(() => {
|
|
327
|
-
if (mounted) {
|
|
328
|
-
console.warn("Auth initialization taking too long, forcing loading to false");
|
|
329
|
-
setLoading(false);
|
|
330
|
-
}
|
|
331
|
-
}, 1e4);
|
|
332
|
-
const initializeAuth = async () => {
|
|
333
|
-
try {
|
|
334
|
-
const { data: { session: initialSession }, error: sessionError } = await supabase.auth.getSession();
|
|
335
|
-
if (!mounted) return;
|
|
336
|
-
if (sessionError) {
|
|
337
|
-
setError(sessionError);
|
|
338
|
-
setLoading(false);
|
|
339
|
-
clearTimeout(safetyTimeout);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
setSession(initialSession);
|
|
343
|
-
if (initialSession?.user) {
|
|
344
|
-
try {
|
|
345
|
-
const userDetails = await fetchUserDetails(initialSession.user);
|
|
346
|
-
if (mounted) setUser(userDetails);
|
|
347
|
-
} catch (err) {
|
|
348
|
-
console.error("Error fetching user details during init:", err);
|
|
349
|
-
if (mounted) {
|
|
350
|
-
setUser({
|
|
351
|
-
id: initialSession.user.id,
|
|
352
|
-
email: initialSession.user.email
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
} catch (err) {
|
|
358
|
-
if (mounted) setError(err instanceof Error ? err : new Error("Failed to initialize auth"));
|
|
359
|
-
} finally {
|
|
360
|
-
if (mounted) {
|
|
361
|
-
setLoading(false);
|
|
362
|
-
clearTimeout(safetyTimeout);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
|
-
initializeAuth();
|
|
367
|
-
const { data: { subscription } } = supabase.auth.onAuthStateChange(async (_event, currentSession) => {
|
|
368
|
-
if (!mounted) return;
|
|
369
|
-
setSession(currentSession);
|
|
370
|
-
setUser(null);
|
|
371
|
-
setLoading(true);
|
|
372
|
-
if (currentSession?.user) {
|
|
373
|
-
try {
|
|
374
|
-
const userDetails = await fetchUserDetails(currentSession.user);
|
|
375
|
-
if (mounted) setUser(userDetails);
|
|
376
|
-
} catch (err) {
|
|
377
|
-
console.error("Error fetching user details on auth state change:", err);
|
|
378
|
-
if (mounted) {
|
|
379
|
-
setUser({
|
|
380
|
-
id: currentSession.user.id,
|
|
381
|
-
email: currentSession.user.email
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
if (mounted) setLoading(false);
|
|
387
|
-
});
|
|
388
|
-
return () => {
|
|
389
|
-
mounted = false;
|
|
390
|
-
clearTimeout(safetyTimeout);
|
|
391
|
-
subscription?.unsubscribe();
|
|
392
|
-
};
|
|
393
|
-
}, [supabase, fetchUserDetails]);
|
|
394
|
-
const signOut = async () => {
|
|
395
|
-
if (!supabase) return;
|
|
396
|
-
setLoading(true);
|
|
397
|
-
const { error: signOutError } = await supabase.auth.signOut();
|
|
398
|
-
if (signOutError) setError(signOutError);
|
|
399
|
-
const logoutRedirectPath = authConfig?.defaultLogoutRedirect || "/login";
|
|
400
|
-
if (router && router.pathname !== logoutRedirectPath && !router.pathname.startsWith(logoutRedirectPath)) {
|
|
401
|
-
router.replace(logoutRedirectPath);
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
return /* @__PURE__ */ jsx(AuthContext.Provider, { value: { session, user, loading, error, signOut }, children });
|
|
405
|
-
};
|
|
406
|
-
var defaultContextValue = {
|
|
407
|
-
components: {},
|
|
408
|
-
hooks: {},
|
|
409
|
-
pages: {}
|
|
410
|
-
};
|
|
411
|
-
var DashboardOverridesContext = createContext(defaultContextValue);
|
|
412
|
-
var DashboardOverridesProvider = ({
|
|
413
|
-
overrides = defaultContextValue,
|
|
414
|
-
children
|
|
415
|
-
}) => {
|
|
416
|
-
const normalizedOverrides = useMemo(() => {
|
|
417
|
-
return {
|
|
418
|
-
components: overrides.components || {},
|
|
419
|
-
hooks: overrides.hooks || {},
|
|
420
|
-
pages: overrides.pages || {}
|
|
421
|
-
};
|
|
422
|
-
}, [overrides]);
|
|
423
|
-
return /* @__PURE__ */ jsx(DashboardOverridesContext.Provider, { value: normalizedOverrides, children });
|
|
424
|
-
};
|
|
425
|
-
function useComponentOverride(key, Default) {
|
|
426
|
-
const { components = {} } = useContext(DashboardOverridesContext);
|
|
427
|
-
return components[key] ?? Default;
|
|
428
|
-
}
|
|
429
|
-
function useHookOverride(key, Default) {
|
|
430
|
-
const { hooks = {} } = useContext(DashboardOverridesContext);
|
|
431
|
-
return hooks[key] ?? Default;
|
|
432
|
-
}
|
|
433
|
-
function usePageOverride(key, Default) {
|
|
434
|
-
const { pages = {} } = useContext(DashboardOverridesContext);
|
|
435
|
-
return pages[key] ?? Default;
|
|
436
|
-
}
|
|
437
|
-
function useOverrides() {
|
|
438
|
-
return useContext(DashboardOverridesContext);
|
|
280
|
+
function useVideoConfig() {
|
|
281
|
+
const { videoConfig } = useDashboardConfig();
|
|
282
|
+
return videoConfig ?? DEFAULT_VIDEO_CONFIG;
|
|
439
283
|
}
|
|
440
284
|
|
|
441
285
|
// src/lib/internal/supabaseClientInstance.ts
|
|
@@ -451,21 +295,31 @@ var _getSupabaseInstance = () => {
|
|
|
451
295
|
}
|
|
452
296
|
return supabaseInstance;
|
|
453
297
|
};
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const contextValue = useMemo(() => ({ supabase: client }), [client]);
|
|
461
|
-
return /* @__PURE__ */ jsx(SupabaseContext.Provider, { value: contextValue, children });
|
|
298
|
+
|
|
299
|
+
// src/lib/services/actionService.ts
|
|
300
|
+
var getTable = (dbConfig, tableName) => {
|
|
301
|
+
const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
|
|
302
|
+
const userValue = dbConfig?.tables?.[tableName];
|
|
303
|
+
return userValue ?? defaults2[tableName];
|
|
462
304
|
};
|
|
463
|
-
var
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
305
|
+
var actionService = {
|
|
306
|
+
async getActionsByName(actionNames, companyIdInput) {
|
|
307
|
+
const supabase = _getSupabaseInstance();
|
|
308
|
+
const config = _getDashboardConfigInstance();
|
|
309
|
+
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
310
|
+
const entityConfig = config.entityConfig;
|
|
311
|
+
const actionsTable = getTable(dbConfig, "actions");
|
|
312
|
+
const targetCompanyId = companyIdInput ?? entityConfig?.companyId;
|
|
313
|
+
if (!targetCompanyId) {
|
|
314
|
+
throw new Error("Company ID must be provided either via entityConfig.companyId or as an argument to getActionsByName.");
|
|
315
|
+
}
|
|
316
|
+
const { data, error } = await supabase.from(actionsTable).select("id, action_name, company_id").eq("company_id", targetCompanyId).in("action_name", actionNames);
|
|
317
|
+
if (error) {
|
|
318
|
+
console.error(`Error fetching actions from table ${actionsTable}:`, error);
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
return data || [];
|
|
467
322
|
}
|
|
468
|
-
return context.supabase;
|
|
469
323
|
};
|
|
470
324
|
var getOperationalDate = (timezone = "Asia/Kolkata", date = /* @__PURE__ */ new Date(), shiftStartTime = "06:00") => {
|
|
471
325
|
const zonedDate = toZonedTime(date, timezone);
|
|
@@ -488,301 +342,57 @@ function getCurrentTimeInZone(timezone, formatString) {
|
|
|
488
342
|
return now2;
|
|
489
343
|
}
|
|
490
344
|
|
|
491
|
-
// src/lib/utils/
|
|
492
|
-
var
|
|
493
|
-
|
|
494
|
-
|
|
345
|
+
// src/lib/utils/shifts.ts
|
|
346
|
+
var DEFAULT_DAY_SHIFT_START = "06:00";
|
|
347
|
+
var DEFAULT_NIGHT_SHIFT_START = "18:00";
|
|
348
|
+
var DEFAULT_TRANSITION_MINUTES = 15;
|
|
349
|
+
var parseTimeToMinutes = (timeString) => {
|
|
350
|
+
if (!timeString || !/^[0-2]\d:[0-5]\d$/.test(timeString)) {
|
|
351
|
+
return NaN;
|
|
352
|
+
}
|
|
353
|
+
const [hours, minutes] = timeString.split(":").map(Number);
|
|
354
|
+
return hours * 60 + minutes;
|
|
495
355
|
};
|
|
496
|
-
var
|
|
497
|
-
|
|
356
|
+
var getCurrentShift = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
|
|
357
|
+
const dayShiftStartStr = shiftConfig?.dayShift?.startTime || DEFAULT_DAY_SHIFT_START;
|
|
358
|
+
const nightShiftStartStr = shiftConfig?.nightShift?.startTime || DEFAULT_NIGHT_SHIFT_START;
|
|
359
|
+
const dayShiftId = shiftConfig?.dayShift?.id ?? 0;
|
|
360
|
+
const nightShiftId = shiftConfig?.nightShift?.id ?? 1;
|
|
361
|
+
const dayShiftStartMinutes = parseTimeToMinutes(dayShiftStartStr);
|
|
362
|
+
const nightShiftStartMinutes = parseTimeToMinutes(nightShiftStartStr);
|
|
363
|
+
const effectiveDayStart = !isNaN(dayShiftStartMinutes) ? dayShiftStartMinutes : parseTimeToMinutes(DEFAULT_DAY_SHIFT_START);
|
|
364
|
+
const effectiveNightStart = !isNaN(nightShiftStartMinutes) ? nightShiftStartMinutes : parseTimeToMinutes(DEFAULT_NIGHT_SHIFT_START);
|
|
365
|
+
const zonedNow = toZonedTime(now2, timezone);
|
|
366
|
+
const currentHour = zonedNow.getHours();
|
|
367
|
+
const currentMinutes = zonedNow.getMinutes();
|
|
368
|
+
const currentTotalMinutes = currentHour * 60 + currentMinutes;
|
|
369
|
+
const operationalDate = getOperationalDate(timezone, zonedNow, dayShiftStartStr);
|
|
370
|
+
let determinedShiftId;
|
|
371
|
+
if (effectiveDayStart < effectiveNightStart) {
|
|
372
|
+
if (currentTotalMinutes >= effectiveDayStart && currentTotalMinutes < effectiveNightStart) {
|
|
373
|
+
determinedShiftId = dayShiftId;
|
|
374
|
+
} else {
|
|
375
|
+
determinedShiftId = nightShiftId;
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
if (currentTotalMinutes >= effectiveDayStart || currentTotalMinutes < effectiveNightStart) {
|
|
379
|
+
determinedShiftId = dayShiftId;
|
|
380
|
+
} else {
|
|
381
|
+
determinedShiftId = nightShiftId;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return { shiftId: determinedShiftId, date: operationalDate };
|
|
498
385
|
};
|
|
499
|
-
|
|
500
|
-
// src/lib/hooks/useMetrics.ts
|
|
501
|
-
var DEFAULT_COMPANY_ID = "default-company-id";
|
|
502
|
-
var useWorkspaceMetrics = (workspaceId) => {
|
|
503
|
-
const supabase = useSupabase();
|
|
504
|
-
const entityConfig = useEntityConfig();
|
|
505
|
-
useDatabaseConfig();
|
|
506
|
-
const dateTimeConfig = useDateTimeConfig();
|
|
507
|
-
const [workspaceMetrics, setWorkspaceMetrics] = useState(null);
|
|
508
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
509
|
-
const [error, setError] = useState(null);
|
|
510
|
-
const fetchWorkspaceMetrics = useCallback(async () => {
|
|
511
|
-
try {
|
|
512
|
-
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
513
|
-
const { data, error: fetchError } = await supabase.from("overview_workspace_metrics").select("*").eq("workspace_id", workspaceId).eq("date", operationalDate).single();
|
|
514
|
-
if (fetchError) throw fetchError;
|
|
515
|
-
setWorkspaceMetrics(data);
|
|
516
|
-
} catch (err) {
|
|
517
|
-
setError({ message: err.message, code: err.code });
|
|
518
|
-
console.error("Error fetching workspace metrics:", err);
|
|
519
|
-
} finally {
|
|
520
|
-
setIsLoading(false);
|
|
521
|
-
}
|
|
522
|
-
}, [supabase, workspaceId, dateTimeConfig.defaultTimezone]);
|
|
523
|
-
useEffect(() => {
|
|
524
|
-
let channels = [];
|
|
525
|
-
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
526
|
-
const setupSubscriptions = () => {
|
|
527
|
-
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
528
|
-
const metricsTablePrefix = getMetricsTablePrefix();
|
|
529
|
-
const metricsTable = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
530
|
-
const metricsChannel = supabase.channel("workspace-metrics").on(
|
|
531
|
-
"postgres_changes",
|
|
532
|
-
{
|
|
533
|
-
event: "*",
|
|
534
|
-
schema: "public",
|
|
535
|
-
table: metricsTable,
|
|
536
|
-
filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
|
|
537
|
-
},
|
|
538
|
-
async (payload) => {
|
|
539
|
-
console.log(`Received ${metricsTablePrefix} update:`, payload);
|
|
540
|
-
await fetchWorkspaceMetrics();
|
|
541
|
-
}
|
|
542
|
-
).subscribe((status) => {
|
|
543
|
-
console.log(`${metricsTablePrefix} subscription status:`, status);
|
|
544
|
-
});
|
|
545
|
-
const overviewChannel = supabase.channel("workspace-overview-metrics").on(
|
|
546
|
-
"postgres_changes",
|
|
547
|
-
{
|
|
548
|
-
event: "*",
|
|
549
|
-
schema: "public",
|
|
550
|
-
table: "overview_workspace_metrics",
|
|
551
|
-
filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
|
|
552
|
-
},
|
|
553
|
-
async (payload) => {
|
|
554
|
-
console.log("Received overview metrics update:", payload);
|
|
555
|
-
await fetchWorkspaceMetrics();
|
|
556
|
-
}
|
|
557
|
-
).subscribe((status) => {
|
|
558
|
-
console.log("Overview metrics subscription status:", status);
|
|
559
|
-
});
|
|
560
|
-
channels = [metricsChannel, overviewChannel];
|
|
561
|
-
};
|
|
562
|
-
fetchWorkspaceMetrics();
|
|
563
|
-
setupSubscriptions();
|
|
564
|
-
return () => {
|
|
565
|
-
channels.forEach((channel) => {
|
|
566
|
-
console.log("Cleaning up channel subscription");
|
|
567
|
-
supabase.removeChannel(channel);
|
|
568
|
-
});
|
|
569
|
-
};
|
|
570
|
-
}, [supabase, workspaceId, fetchWorkspaceMetrics, entityConfig.companyId, dateTimeConfig.defaultTimezone]);
|
|
571
|
-
return { workspaceMetrics, isLoading, error, refetch: fetchWorkspaceMetrics };
|
|
572
|
-
};
|
|
573
|
-
var useLineMetrics = (lineId) => {
|
|
574
|
-
const supabase = useSupabase();
|
|
575
|
-
const dateTimeConfig = useDateTimeConfig();
|
|
576
|
-
const [lineMetrics, setLineMetrics] = useState(null);
|
|
577
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
578
|
-
const [error, setError] = useState(null);
|
|
579
|
-
const fetchLineMetrics = useCallback(async () => {
|
|
580
|
-
try {
|
|
581
|
-
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
582
|
-
const { data, error: fetchError } = await supabase.from("overview_line_metrics").select("*").eq("line_id", lineId).eq("date", operationalDate).single();
|
|
583
|
-
if (fetchError) throw fetchError;
|
|
584
|
-
setLineMetrics(data);
|
|
585
|
-
} catch (err) {
|
|
586
|
-
setError({ message: err.message, code: err.code });
|
|
587
|
-
console.error("Error fetching line metrics:", err);
|
|
588
|
-
} finally {
|
|
589
|
-
setIsLoading(false);
|
|
590
|
-
}
|
|
591
|
-
}, [supabase, lineId, dateTimeConfig.defaultTimezone]);
|
|
592
|
-
useEffect(() => {
|
|
593
|
-
let channels = [];
|
|
594
|
-
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
595
|
-
const setupSubscriptions = () => {
|
|
596
|
-
const lineMetricsChannel = supabase.channel("line-base-metrics").on(
|
|
597
|
-
"postgres_changes",
|
|
598
|
-
{
|
|
599
|
-
event: "*",
|
|
600
|
-
schema: "public",
|
|
601
|
-
table: "line_metrics",
|
|
602
|
-
filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
|
|
603
|
-
},
|
|
604
|
-
async (payload) => {
|
|
605
|
-
console.log("Received line metrics update:", payload);
|
|
606
|
-
await fetchLineMetrics();
|
|
607
|
-
}
|
|
608
|
-
).subscribe((status) => {
|
|
609
|
-
console.log("Line metrics subscription status:", status);
|
|
610
|
-
});
|
|
611
|
-
const overviewChannel = supabase.channel("line-overview-metrics").on(
|
|
612
|
-
"postgres_changes",
|
|
613
|
-
{
|
|
614
|
-
event: "*",
|
|
615
|
-
schema: "public",
|
|
616
|
-
table: "overview_line_metrics",
|
|
617
|
-
filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
|
|
618
|
-
},
|
|
619
|
-
async (payload) => {
|
|
620
|
-
console.log("Received line overview update:", payload);
|
|
621
|
-
await fetchLineMetrics();
|
|
622
|
-
}
|
|
623
|
-
).subscribe((status) => {
|
|
624
|
-
console.log("Line overview subscription status:", status);
|
|
625
|
-
});
|
|
626
|
-
channels = [lineMetricsChannel, overviewChannel];
|
|
627
|
-
};
|
|
628
|
-
fetchLineMetrics();
|
|
629
|
-
setupSubscriptions();
|
|
630
|
-
return () => {
|
|
631
|
-
channels.forEach((channel) => {
|
|
632
|
-
console.log("Cleaning up channel subscription");
|
|
633
|
-
supabase.removeChannel(channel);
|
|
634
|
-
});
|
|
635
|
-
};
|
|
636
|
-
}, [supabase, lineId, fetchLineMetrics, dateTimeConfig.defaultTimezone]);
|
|
637
|
-
return { lineMetrics, isLoading, error, refetch: fetchLineMetrics };
|
|
638
|
-
};
|
|
639
|
-
var useMetrics = (tableName, options) => {
|
|
640
|
-
const supabase = useSupabase();
|
|
641
|
-
const entityConfig = useEntityConfig();
|
|
642
|
-
const [data, setData] = useState([]);
|
|
643
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
644
|
-
const [error, setError] = useState(null);
|
|
645
|
-
const channelRef = useRef(null);
|
|
646
|
-
useEffect(() => {
|
|
647
|
-
const fetchData = async () => {
|
|
648
|
-
try {
|
|
649
|
-
setIsLoading(true);
|
|
650
|
-
setError(null);
|
|
651
|
-
let actualTableName = tableName;
|
|
652
|
-
if (tableName === "metrics") {
|
|
653
|
-
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
654
|
-
const metricsTablePrefix = getMetricsTablePrefix(companyId);
|
|
655
|
-
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
656
|
-
}
|
|
657
|
-
let query = supabase.from(actualTableName).select("*");
|
|
658
|
-
if (options?.filter) {
|
|
659
|
-
Object.entries(options.filter).forEach(([key, value]) => {
|
|
660
|
-
query = query.eq(key, value);
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
const { data: result, error: fetchError } = await query;
|
|
664
|
-
if (fetchError) throw fetchError;
|
|
665
|
-
setData(result);
|
|
666
|
-
} catch (err) {
|
|
667
|
-
console.error(`Error fetching data from ${tableName}:`, err);
|
|
668
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
669
|
-
} finally {
|
|
670
|
-
setIsLoading(false);
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
|
-
const setupSubscription = () => {
|
|
674
|
-
if (!options?.realtime) return;
|
|
675
|
-
let actualTableName = tableName;
|
|
676
|
-
if (tableName === "metrics") {
|
|
677
|
-
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
678
|
-
const metricsTablePrefix = getMetricsTablePrefix();
|
|
679
|
-
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
680
|
-
}
|
|
681
|
-
const filter2 = {};
|
|
682
|
-
if (options?.filter) {
|
|
683
|
-
Object.entries(options.filter).forEach(([key, value]) => {
|
|
684
|
-
filter2[`${key}=eq.${value}`] = value;
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
channelRef.current = supabase.channel(`${tableName}-changes`).on(
|
|
688
|
-
"postgres_changes",
|
|
689
|
-
{
|
|
690
|
-
event: "*",
|
|
691
|
-
schema: "public",
|
|
692
|
-
table: actualTableName,
|
|
693
|
-
filter: Object.keys(filter2).length > 0 ? Object.keys(filter2).join(",") : void 0
|
|
694
|
-
},
|
|
695
|
-
() => {
|
|
696
|
-
fetchData();
|
|
697
|
-
}
|
|
698
|
-
).subscribe();
|
|
699
|
-
};
|
|
700
|
-
fetchData();
|
|
701
|
-
setupSubscription();
|
|
702
|
-
return () => {
|
|
703
|
-
if (channelRef.current) {
|
|
704
|
-
supabase.removeChannel(channelRef.current);
|
|
705
|
-
}
|
|
706
|
-
};
|
|
707
|
-
}, [supabase, tableName, options, entityConfig.companyId]);
|
|
708
|
-
const refetch = async () => {
|
|
709
|
-
setIsLoading(true);
|
|
710
|
-
try {
|
|
711
|
-
let actualTableName = tableName;
|
|
712
|
-
if (tableName === "metrics") {
|
|
713
|
-
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
714
|
-
const metricsTablePrefix = getMetricsTablePrefix(companyId);
|
|
715
|
-
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
716
|
-
}
|
|
717
|
-
let query = supabase.from(actualTableName).select("*");
|
|
718
|
-
if (options?.filter) {
|
|
719
|
-
Object.entries(options.filter).forEach(([key, value]) => {
|
|
720
|
-
query = query.eq(key, value);
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
const { data: result, error: fetchError } = await query;
|
|
724
|
-
if (fetchError) throw fetchError;
|
|
725
|
-
setData(result);
|
|
726
|
-
setError(null);
|
|
727
|
-
} catch (err) {
|
|
728
|
-
console.error(`Error refetching data from ${tableName}:`, err);
|
|
729
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
730
|
-
} finally {
|
|
731
|
-
setIsLoading(false);
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
return { data, isLoading, error, refetch };
|
|
735
|
-
};
|
|
736
|
-
var DEFAULT_DAY_SHIFT_START = "06:00";
|
|
737
|
-
var DEFAULT_NIGHT_SHIFT_START = "18:00";
|
|
738
|
-
var DEFAULT_TRANSITION_MINUTES = 15;
|
|
739
|
-
var parseTimeToMinutes = (timeString) => {
|
|
740
|
-
if (!timeString || !/^[0-2]\d:[0-5]\d$/.test(timeString)) {
|
|
741
|
-
return NaN;
|
|
742
|
-
}
|
|
743
|
-
const [hours, minutes] = timeString.split(":").map(Number);
|
|
744
|
-
return hours * 60 + minutes;
|
|
745
|
-
};
|
|
746
|
-
var getCurrentShift = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
|
|
386
|
+
var isTransitionPeriod = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
|
|
747
387
|
const dayShiftStartStr = shiftConfig?.dayShift?.startTime || DEFAULT_DAY_SHIFT_START;
|
|
748
388
|
const nightShiftStartStr = shiftConfig?.nightShift?.startTime || DEFAULT_NIGHT_SHIFT_START;
|
|
749
|
-
const
|
|
750
|
-
const nightShiftId = shiftConfig?.nightShift?.id ?? 1;
|
|
389
|
+
const transitionMinutes = shiftConfig?.transitionPeriodMinutes ?? DEFAULT_TRANSITION_MINUTES;
|
|
751
390
|
const dayShiftStartMinutes = parseTimeToMinutes(dayShiftStartStr);
|
|
752
391
|
const nightShiftStartMinutes = parseTimeToMinutes(nightShiftStartStr);
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const
|
|
757
|
-
const currentMinutes = zonedNow.getMinutes();
|
|
758
|
-
const currentTotalMinutes = currentHour * 60 + currentMinutes;
|
|
759
|
-
const operationalDate = getOperationalDate(timezone, zonedNow, dayShiftStartStr);
|
|
760
|
-
let determinedShiftId;
|
|
761
|
-
if (effectiveDayStart < effectiveNightStart) {
|
|
762
|
-
if (currentTotalMinutes >= effectiveDayStart && currentTotalMinutes < effectiveNightStart) {
|
|
763
|
-
determinedShiftId = dayShiftId;
|
|
764
|
-
} else {
|
|
765
|
-
determinedShiftId = nightShiftId;
|
|
766
|
-
}
|
|
767
|
-
} else {
|
|
768
|
-
if (currentTotalMinutes >= effectiveDayStart || currentTotalMinutes < effectiveNightStart) {
|
|
769
|
-
determinedShiftId = dayShiftId;
|
|
770
|
-
} else {
|
|
771
|
-
determinedShiftId = nightShiftId;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
return { shiftId: determinedShiftId, date: operationalDate };
|
|
775
|
-
};
|
|
776
|
-
var isTransitionPeriod = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
|
|
777
|
-
const dayShiftStartStr = shiftConfig?.dayShift?.startTime || DEFAULT_DAY_SHIFT_START;
|
|
778
|
-
const nightShiftStartStr = shiftConfig?.nightShift?.startTime || DEFAULT_NIGHT_SHIFT_START;
|
|
779
|
-
const transitionMinutes = shiftConfig?.transitionPeriodMinutes ?? DEFAULT_TRANSITION_MINUTES;
|
|
780
|
-
const dayShiftStartMinutes = parseTimeToMinutes(dayShiftStartStr);
|
|
781
|
-
const nightShiftStartMinutes = parseTimeToMinutes(nightShiftStartStr);
|
|
782
|
-
if (isNaN(dayShiftStartMinutes) || isNaN(nightShiftStartMinutes)) {
|
|
783
|
-
return false;
|
|
784
|
-
}
|
|
785
|
-
const transitionTimes = [dayShiftStartMinutes, nightShiftStartMinutes];
|
|
392
|
+
if (isNaN(dayShiftStartMinutes) || isNaN(nightShiftStartMinutes)) {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
const transitionTimes = [dayShiftStartMinutes, nightShiftStartMinutes];
|
|
786
396
|
const zonedNow = toZonedTime(now2, timezone);
|
|
787
397
|
const currentHour = zonedNow.getHours();
|
|
788
398
|
const currentMinutes = zonedNow.getMinutes();
|
|
@@ -808,748 +418,246 @@ var isTransitionPeriod = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date
|
|
|
808
418
|
});
|
|
809
419
|
};
|
|
810
420
|
|
|
811
|
-
// src/lib/
|
|
812
|
-
var
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
const
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
console.log(`[useWorkspaceDetailedMetrics] Found fallback data from date: ${recentData.date}, shift: ${recentData.shift_id}`);
|
|
851
|
-
const outputDifference2 = (recentData.total_output || 0) - (recentData.ideal_output || 0);
|
|
852
|
-
const workspaceMatch2 = recentData.workspace_name?.match(/WS(\d+)/);
|
|
853
|
-
const workspaceNumber2 = workspaceMatch2 ? parseInt(workspaceMatch2[1]) : 0;
|
|
854
|
-
const specialWsStart2 = workspaceConfig.specialWorkspaces?.startId ?? 19;
|
|
855
|
-
const specialWsEnd2 = workspaceConfig.specialWorkspaces?.endId ?? 34;
|
|
856
|
-
const isSpecialWorkspace2 = workspaceNumber2 >= specialWsStart2 && workspaceNumber2 <= specialWsEnd2;
|
|
857
|
-
const outputHourly2 = recentData.output_hourly || {};
|
|
858
|
-
const hasOutputHourlyData2 = outputHourly2 && typeof outputHourly2 === "object" && Object.keys(outputHourly2).length > 0;
|
|
859
|
-
let hourlyActionCounts2 = [];
|
|
860
|
-
if (hasOutputHourlyData2) {
|
|
861
|
-
console.log("Using new output_hourly column for workspace (fallback):", recentData.workspace_name);
|
|
862
|
-
console.log("Raw output_hourly data (fallback):", outputHourly2);
|
|
863
|
-
const isNightShift = recentData.shift_id === 1;
|
|
864
|
-
const shiftStart = recentData.shift_start || (isNightShift ? "22:00" : "06:00");
|
|
865
|
-
const shiftEnd = recentData.shift_end || (isNightShift ? "06:00" : "14:00");
|
|
866
|
-
const startHour = parseInt(shiftStart.split(":")[0]);
|
|
867
|
-
let expectedHours = [];
|
|
868
|
-
if (isNightShift) {
|
|
869
|
-
for (let i = 0; i < 9; i++) {
|
|
870
|
-
expectedHours.push((startHour + i) % 24);
|
|
871
|
-
}
|
|
872
|
-
} else {
|
|
873
|
-
for (let i = 0; i < 9; i++) {
|
|
874
|
-
expectedHours.push((startHour + i) % 24);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
console.log("Expected shift hours (fallback):", expectedHours);
|
|
878
|
-
console.log("Available data hours (fallback):", Object.keys(outputHourly2));
|
|
879
|
-
hourlyActionCounts2 = expectedHours.map((expectedHour) => {
|
|
880
|
-
let hourData = outputHourly2[expectedHour.toString()];
|
|
881
|
-
if (!hourData && isNightShift) {
|
|
882
|
-
for (const [storedHour, data2] of Object.entries(outputHourly2)) {
|
|
883
|
-
if (Array.isArray(data2) && data2.length > 0 && data2.some((val) => val > 0)) {
|
|
884
|
-
if (storedHour === "18" && expectedHour === 1) {
|
|
885
|
-
hourData = data2;
|
|
886
|
-
console.log(`Mapping stored hour ${storedHour} to expected hour ${expectedHour} (fallback)`);
|
|
887
|
-
break;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
return Array.isArray(hourData) ? hourData.reduce((sum, count) => sum + (count || 0), 0) : 0;
|
|
893
|
-
});
|
|
894
|
-
console.log("Final hourly action counts (fallback):", hourlyActionCounts2);
|
|
895
|
-
} else {
|
|
896
|
-
console.log("Using output_array fallback for workspace (fallback):", recentData.workspace_name);
|
|
897
|
-
const minuteByMinuteArray = recentData.output_array || [];
|
|
898
|
-
if (isSpecialWorkspace2) {
|
|
899
|
-
const last40Readings = minuteByMinuteArray.slice(Math.max(0, minuteByMinuteArray.length - 40));
|
|
900
|
-
hourlyActionCounts2 = last40Readings;
|
|
901
|
-
} else {
|
|
902
|
-
for (let i = 0; i < minuteByMinuteArray.length; i += 60) {
|
|
903
|
-
const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
|
|
904
|
-
const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
|
|
905
|
-
hourlyActionCounts2.push(hourlySum);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
const transformedData2 = {
|
|
910
|
-
workspace_id: recentData.workspace_id,
|
|
911
|
-
workspace_name: recentData.workspace_name,
|
|
912
|
-
line_id: recentData.line_id,
|
|
913
|
-
line_name: recentData.line_name || "Line 1",
|
|
914
|
-
company_id: recentData.company_id || companyId,
|
|
915
|
-
company_name: recentData.company_name || "Nahar Group",
|
|
916
|
-
date: recentData.date,
|
|
917
|
-
shift_id: recentData.shift_id,
|
|
918
|
-
action_name: recentData.action_name || "",
|
|
919
|
-
shift_start: recentData.shift_start || "06:00",
|
|
920
|
-
shift_end: recentData.shift_end || "14:00",
|
|
921
|
-
shift_type: recentData.shift_type || (recentData.shift_id === 0 ? "Day" : "Night"),
|
|
922
|
-
pph_threshold: recentData.pph_threshold || 0,
|
|
923
|
-
target_output: recentData.total_day_output || 0,
|
|
924
|
-
avg_pph: recentData.avg_pph || 0,
|
|
925
|
-
avg_cycle_time: recentData.avg_cycle_time || 0,
|
|
926
|
-
ideal_cycle_time: recentData.ideal_cycle_time || 0,
|
|
927
|
-
avg_efficiency: recentData.efficiency || 0,
|
|
928
|
-
total_actions: recentData.total_output || 0,
|
|
929
|
-
hourly_action_counts: hourlyActionCounts2,
|
|
930
|
-
// Now uses the NEW logic with fallback
|
|
931
|
-
workspace_rank: recentData.workspace_rank || 0,
|
|
932
|
-
total_workspaces: recentData.total_workspaces || workspaceConfig.totalWorkspaces || 42,
|
|
933
|
-
ideal_output_until_now: recentData.ideal_output || 0,
|
|
934
|
-
output_difference: outputDifference2,
|
|
935
|
-
idle_time: recentData.idle_time || 0,
|
|
936
|
-
idle_time_hourly: recentData.idle_time_hourly || void 0,
|
|
937
|
-
...recentData.compliance_efficiency !== void 0 && { compliance_efficiency: recentData.compliance_efficiency },
|
|
938
|
-
...recentData.sop_check !== void 0 && { sop_check: recentData.sop_check }
|
|
939
|
-
};
|
|
940
|
-
setMetrics(transformedData2);
|
|
941
|
-
setIsLoading(false);
|
|
942
|
-
updateQueueRef.current = false;
|
|
943
|
-
isFetchingRef.current = false;
|
|
944
|
-
return;
|
|
945
|
-
} else {
|
|
946
|
-
console.warn("[useWorkspaceDetailedMetrics] No data found for workspace:", workspaceId, "at all");
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
if (!data) {
|
|
950
|
-
console.warn("[useWorkspaceDetailedMetrics] No detailed metrics found for workspace:", workspaceId);
|
|
951
|
-
setMetrics(null);
|
|
952
|
-
setIsLoading(false);
|
|
953
|
-
updateQueueRef.current = false;
|
|
954
|
-
isFetchingRef.current = false;
|
|
955
|
-
return;
|
|
421
|
+
// src/lib/utils/database.ts
|
|
422
|
+
var getCompanyMetricsTableName = (companyId, prefix = "workspace_performance") => {
|
|
423
|
+
if (!companyId) return `${prefix}_unknown_company`;
|
|
424
|
+
return `${prefix}_${companyId.replace(/-/g, "_")}`;
|
|
425
|
+
};
|
|
426
|
+
var getMetricsTablePrefix = (companyId) => {
|
|
427
|
+
return "performance_metrics";
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// src/lib/services/dashboardService.ts
|
|
431
|
+
var getTable2 = (dbConfig, tableName) => {
|
|
432
|
+
const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
|
|
433
|
+
const userValue = dbConfig?.tables?.[tableName];
|
|
434
|
+
return userValue ?? defaults2[tableName];
|
|
435
|
+
};
|
|
436
|
+
var dashboardService = {
|
|
437
|
+
// Example for getLineInfo:
|
|
438
|
+
async getLineInfo(lineIdInput) {
|
|
439
|
+
const supabase = _getSupabaseInstance();
|
|
440
|
+
const config = _getDashboardConfigInstance();
|
|
441
|
+
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
442
|
+
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
443
|
+
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
444
|
+
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
445
|
+
const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
|
|
446
|
+
const linesTable = getTable2(dbConfig, "lines");
|
|
447
|
+
const lineMetricsTable = getTable2(dbConfig, "lineMetrics");
|
|
448
|
+
const companyId = entityConfig.companyId;
|
|
449
|
+
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
450
|
+
const metricsTable = `${metricsTablePrefixStr}_${companyId ? companyId.replace(/-/g, "_") : "unknown_company"}`;
|
|
451
|
+
const defaultLineId = entityConfig.defaultLineId;
|
|
452
|
+
const secondaryLineId = entityConfig.secondaryLineId;
|
|
453
|
+
const factoryViewId = entityConfig.factoryViewId ?? "factory";
|
|
454
|
+
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
455
|
+
const { shiftId, date } = getCurrentShift(defaultTimezone, shiftConfig);
|
|
456
|
+
const lineId = lineIdInput;
|
|
457
|
+
if (lineId === factoryViewId) {
|
|
458
|
+
if (!defaultLineId || !secondaryLineId || !companyId) {
|
|
459
|
+
throw new Error("Factory View requires defaultLineId, secondaryLineId, and companyId to be configured.");
|
|
956
460
|
}
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
|
|
1018
|
-
const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
|
|
1019
|
-
hourlyActionCounts.push(hourlySum);
|
|
1020
|
-
}
|
|
461
|
+
const { data: lineData2, error: lineError2 } = await supabase.from(linesTable).select(`
|
|
462
|
+
id,
|
|
463
|
+
line_name,
|
|
464
|
+
factory_id,
|
|
465
|
+
factories!lines_factory_id_fkey(factory_name),
|
|
466
|
+
company_id,
|
|
467
|
+
companies!lines_company_id_fkey(company_name:name)
|
|
468
|
+
`).eq("id", defaultLineId).maybeSingle();
|
|
469
|
+
if (lineError2) throw lineError2;
|
|
470
|
+
if (!lineData2) throw new Error(`Configured default line (${defaultLineId}) not found`);
|
|
471
|
+
const { data: metricsData, error: metricsError } = await supabase.from(lineMetricsTable).select("*").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
|
|
472
|
+
if (metricsError) throw metricsError;
|
|
473
|
+
const { data: performanceData2, error: performanceError2 } = await supabase.from(metricsTable).select("efficiency").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
|
|
474
|
+
if (performanceError2) throw performanceError2;
|
|
475
|
+
const underperformingCount2 = performanceData2?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
|
|
476
|
+
const totalValidWorkspaces2 = performanceData2?.filter((w) => w.efficiency >= 10).length || 0;
|
|
477
|
+
const combinedMetrics = (metricsData || []).reduce((acc, m) => {
|
|
478
|
+
acc.avg_efficiency += m.efficiency ?? 0;
|
|
479
|
+
acc.current_output += m.current_output ?? 0;
|
|
480
|
+
acc.ideal_output += m.ideal_output ?? m.line_threshold ?? 0;
|
|
481
|
+
return acc;
|
|
482
|
+
}, { avg_efficiency: 0, current_output: 0, ideal_output: 0 });
|
|
483
|
+
metricsData?.length || 1;
|
|
484
|
+
return {
|
|
485
|
+
line_id: factoryViewId,
|
|
486
|
+
// Use configured factory view ID
|
|
487
|
+
line_name: "Factory View",
|
|
488
|
+
// Consider making this configurable?
|
|
489
|
+
company_id: lineData2.company_id,
|
|
490
|
+
company_name: lineData2.companies?.[0]?.company_name ?? "",
|
|
491
|
+
factory_id: lineData2.factory_id,
|
|
492
|
+
factory_name: lineData2.factories?.[0]?.factory_name ?? "",
|
|
493
|
+
shift_id: shiftId,
|
|
494
|
+
date,
|
|
495
|
+
metrics: {
|
|
496
|
+
avg_efficiency: (performanceData2?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces2 || 1),
|
|
497
|
+
// Use performance data for avg efficiency
|
|
498
|
+
avg_cycle_time: 0,
|
|
499
|
+
// Needs calculation logic if required for factory view
|
|
500
|
+
current_output: combinedMetrics.current_output,
|
|
501
|
+
ideal_output: combinedMetrics.ideal_output,
|
|
502
|
+
total_workspaces: 44,
|
|
503
|
+
// SRC ALIGNMENT: Use hardcoded 44 for factory view total_workspaces
|
|
504
|
+
underperforming_workspaces: underperformingCount2,
|
|
505
|
+
underperforming_workspace_names: [],
|
|
506
|
+
// Populate if needed
|
|
507
|
+
underperforming_workspace_uuids: [],
|
|
508
|
+
// Populate if needed
|
|
509
|
+
output_array: [],
|
|
510
|
+
// Combine if needed
|
|
511
|
+
line_threshold: combinedMetrics.ideal_output,
|
|
512
|
+
threshold_pph: 0,
|
|
513
|
+
// Needs calculation logic if required
|
|
514
|
+
shift_start: shiftConfig.dayShift?.startTime || "06:00",
|
|
515
|
+
// Use config
|
|
516
|
+
shift_end: shiftConfig.dayShift?.endTime || "18:00",
|
|
517
|
+
// Use config
|
|
518
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
519
|
+
poorest_performing_workspaces: []
|
|
520
|
+
// Populate if needed
|
|
1021
521
|
}
|
|
1022
|
-
console.log("Final hourly action counts:", hourlyActionCounts);
|
|
1023
|
-
}
|
|
1024
|
-
const transformedData = {
|
|
1025
|
-
workspace_id: data.workspace_id,
|
|
1026
|
-
workspace_name: data.workspace_name,
|
|
1027
|
-
line_id: data.line_id,
|
|
1028
|
-
line_name: data.line_name || "Line 1",
|
|
1029
|
-
company_id: data.company_id || companyId,
|
|
1030
|
-
company_name: data.company_name || "Nahar Group",
|
|
1031
|
-
date: data.date,
|
|
1032
|
-
shift_id: data.shift_id,
|
|
1033
|
-
action_name: data.action_name || "",
|
|
1034
|
-
shift_start: data.shift_start || "06:00",
|
|
1035
|
-
shift_end: data.shift_end || "14:00",
|
|
1036
|
-
shift_type: data.shift_type || (data.shift_id === 0 ? "Day" : "Night"),
|
|
1037
|
-
pph_threshold: data.pph_threshold || 0,
|
|
1038
|
-
target_output: data.total_day_output || 0,
|
|
1039
|
-
avg_pph: data.avg_pph || 0,
|
|
1040
|
-
avg_cycle_time: data.avg_cycle_time || 0,
|
|
1041
|
-
ideal_cycle_time: data.ideal_cycle_time || 0,
|
|
1042
|
-
avg_efficiency: data.efficiency || 0,
|
|
1043
|
-
total_actions: data.total_output || 0,
|
|
1044
|
-
hourly_action_counts: hourlyActionCounts,
|
|
1045
|
-
workspace_rank: data.workspace_rank || 0,
|
|
1046
|
-
total_workspaces: data.total_workspaces || workspaceConfig.totalWorkspaces || 42,
|
|
1047
|
-
ideal_output_until_now: data.ideal_output || 0,
|
|
1048
|
-
output_difference: outputDifference,
|
|
1049
|
-
idle_time: data.idle_time || 0,
|
|
1050
|
-
// Add idle_time from performance_metrics table
|
|
1051
|
-
idle_time_hourly: data.idle_time_hourly || void 0,
|
|
1052
|
-
// Add idle_time_hourly from performance_metrics table
|
|
1053
|
-
...data.compliance_efficiency !== void 0 && { compliance_efficiency: data.compliance_efficiency },
|
|
1054
|
-
...data.sop_check !== void 0 && { sop_check: data.sop_check }
|
|
1055
522
|
};
|
|
1056
|
-
|
|
523
|
+
}
|
|
524
|
+
if (!companyId) {
|
|
525
|
+
throw new Error("Company ID must be configured for individual line requests.");
|
|
526
|
+
}
|
|
527
|
+
const { data: lineData, error: lineError } = await supabase.from(linesTable).select("id, line_name, factory_id, factories!lines_factory_id_fkey(factory_name), company_id, companies!lines_company_id_fkey(company_name:name)").eq("id", lineId).maybeSingle();
|
|
528
|
+
if (lineError) throw lineError;
|
|
529
|
+
if (!lineData) throw new Error(`Line with ID ${lineId} not found`);
|
|
530
|
+
let metricsFromDb = null;
|
|
531
|
+
try {
|
|
532
|
+
const { data: fetchedMetrics, error } = await supabase.from(lineMetricsTable).select("*").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date).maybeSingle();
|
|
533
|
+
if (error) throw error;
|
|
534
|
+
metricsFromDb = fetchedMetrics;
|
|
1057
535
|
} catch (err) {
|
|
1058
|
-
console.error(
|
|
1059
|
-
setError({ message: err.message, code: err.code });
|
|
1060
|
-
} finally {
|
|
1061
|
-
isFetchingRef.current = false;
|
|
1062
|
-
updateQueueRef.current = false;
|
|
1063
|
-
setIsLoading(false);
|
|
536
|
+
console.error(`Error fetching line metrics for ${lineId}:`, err);
|
|
1064
537
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
538
|
+
const { data: performanceData, error: performanceError } = await supabase.from(metricsTable).select("efficiency").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date);
|
|
539
|
+
if (performanceError) throw performanceError;
|
|
540
|
+
const underperformingCount = performanceData?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
|
|
541
|
+
const totalValidWorkspaces = performanceData?.filter((w) => w.efficiency >= 10).length || 0;
|
|
542
|
+
const avgEfficiencyFromPerf = (performanceData?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces || 1);
|
|
543
|
+
const useFallbackMetrics = !metricsFromDb;
|
|
544
|
+
let metricsForReturn;
|
|
545
|
+
if (useFallbackMetrics) {
|
|
546
|
+
metricsForReturn = {
|
|
547
|
+
avg_efficiency: 0,
|
|
548
|
+
avg_cycle_time: 0,
|
|
549
|
+
current_output: 0,
|
|
550
|
+
ideal_output: 0,
|
|
551
|
+
total_workspaces: 42,
|
|
552
|
+
underperforming_workspaces: underperformingCount,
|
|
553
|
+
underperforming_workspace_names: [],
|
|
554
|
+
underperforming_workspace_uuids: [],
|
|
555
|
+
output_array: [],
|
|
556
|
+
line_threshold: 0,
|
|
557
|
+
threshold_pph: 0,
|
|
558
|
+
shift_start: "06:00",
|
|
559
|
+
shift_end: "14:00",
|
|
560
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
561
|
+
poorest_performing_workspaces: []
|
|
562
|
+
};
|
|
563
|
+
} else {
|
|
564
|
+
metricsForReturn = {
|
|
565
|
+
avg_efficiency: metricsFromDb.efficiency ?? avgEfficiencyFromPerf,
|
|
566
|
+
avg_cycle_time: metricsFromDb.avg_cycle_time || 0,
|
|
567
|
+
current_output: metricsFromDb.current_output || 0,
|
|
568
|
+
ideal_output: metricsFromDb.ideal_output || metricsFromDb.line_threshold || 0,
|
|
569
|
+
total_workspaces: metricsFromDb.total_workspaces ?? workspaceConfig.totalWorkspaces ?? 42,
|
|
570
|
+
underperforming_workspaces: underperformingCount,
|
|
571
|
+
underperforming_workspace_names: metricsFromDb.underperforming_workspace_names || [],
|
|
572
|
+
underperforming_workspace_uuids: metricsFromDb.underperforming_workspace_uuids || [],
|
|
573
|
+
output_array: metricsFromDb.output_array || [],
|
|
574
|
+
line_threshold: metricsFromDb.line_threshold || 0,
|
|
575
|
+
threshold_pph: metricsFromDb.threshold_pph || 0,
|
|
576
|
+
shift_start: metricsFromDb.shift_start || shiftConfig.dayShift?.startTime || "06:00",
|
|
577
|
+
shift_end: metricsFromDb.shift_end || shiftConfig.dayShift?.endTime || "18:00",
|
|
578
|
+
last_updated: metricsFromDb.last_updated || (/* @__PURE__ */ new Date()).toISOString(),
|
|
579
|
+
poorest_performing_workspaces: metricsFromDb.poorest_performing_workspaces || []
|
|
580
|
+
};
|
|
581
|
+
if (metricsFromDb.efficiency === null || metricsFromDb.efficiency === void 0) {
|
|
582
|
+
metricsForReturn.avg_efficiency = avgEfficiencyFromPerf;
|
|
583
|
+
}
|
|
1071
584
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
585
|
+
return {
|
|
586
|
+
line_id: lineData.id,
|
|
587
|
+
line_name: lineData.line_name,
|
|
588
|
+
company_id: lineData.company_id,
|
|
589
|
+
company_name: lineData.companies?.[0]?.company_name ?? "",
|
|
590
|
+
factory_id: lineData.factory_id,
|
|
591
|
+
factory_name: lineData.factories?.[0]?.factory_name ?? "",
|
|
592
|
+
shift_id: shiftId,
|
|
593
|
+
date,
|
|
594
|
+
metrics: metricsForReturn
|
|
595
|
+
};
|
|
596
|
+
},
|
|
597
|
+
async getWorkspacesData(lineIdInput, dateProp, shiftProp) {
|
|
598
|
+
const supabase = _getSupabaseInstance();
|
|
599
|
+
const config = _getDashboardConfigInstance();
|
|
600
|
+
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
601
|
+
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
602
|
+
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
603
|
+
const companyId = entityConfig.companyId;
|
|
604
|
+
if (!companyId) {
|
|
605
|
+
throw new Error("Company ID must be configured for workspace data requests.");
|
|
1080
606
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
607
|
+
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
608
|
+
const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
|
|
609
|
+
const defaultLineId = entityConfig.defaultLineId;
|
|
610
|
+
const secondaryLineId = entityConfig.secondaryLineId;
|
|
611
|
+
const factoryViewId = entityConfig.factoryViewId ?? "factory";
|
|
612
|
+
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
613
|
+
const currentShiftResult = getCurrentShift(defaultTimezone, shiftConfig);
|
|
614
|
+
const queryDate = dateProp || getOperationalDate(defaultTimezone);
|
|
615
|
+
const queryShiftId = shiftProp ?? currentShiftResult.shiftId;
|
|
616
|
+
const lineId = lineIdInput;
|
|
617
|
+
let query = supabase.from(metricsTable).select("company_id,line_id,shift_id,date,workspace_id,workspace_name,total_output,avg_pph,performance_score,avg_cycle_time,trend_score,ideal_output,efficiency,total_day_output").eq("shift_id", queryShiftId).eq("date", queryDate);
|
|
618
|
+
if (!lineId || lineId === factoryViewId) {
|
|
619
|
+
if (!defaultLineId || !secondaryLineId) {
|
|
620
|
+
throw new Error("Factory View requires defaultLineId and secondaryLineId to be configured for workspace data.");
|
|
1092
621
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
}, [supabase, workspaceId, fetchMetrics, schema, metricsTable, metricsTablePrefix]);
|
|
1097
|
-
useEffect(() => {
|
|
1098
|
-
if (!workspaceId) {
|
|
1099
|
-
setIsLoading(false);
|
|
1100
|
-
return;
|
|
622
|
+
query = query.in("line_id", [defaultLineId, secondaryLineId]);
|
|
623
|
+
} else {
|
|
624
|
+
query = query.eq("line_id", lineId);
|
|
1101
625
|
}
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
const metricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
|
|
1107
|
-
"postgres_changes",
|
|
1108
|
-
{
|
|
1109
|
-
event: "*",
|
|
1110
|
-
schema,
|
|
1111
|
-
table: metricsTable,
|
|
1112
|
-
filter: `workspace_id=eq.${workspaceId}`
|
|
1113
|
-
},
|
|
1114
|
-
async (payload) => {
|
|
1115
|
-
const payloadData = payload.new;
|
|
1116
|
-
console.log(`Received ${metricsTablePrefix} update:`, {
|
|
1117
|
-
payload,
|
|
1118
|
-
payloadDate: payloadData?.date,
|
|
1119
|
-
payloadShift: payloadData?.shift_id,
|
|
1120
|
-
currentDate: operationalDate,
|
|
1121
|
-
currentShift: queryShiftId,
|
|
1122
|
-
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
1123
|
-
});
|
|
1124
|
-
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
1125
|
-
queueUpdate();
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
).subscribe((status) => {
|
|
1129
|
-
console.log(`${metricsTablePrefix} subscription status:`, status);
|
|
1130
|
-
});
|
|
1131
|
-
const workspaceMetricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
|
|
1132
|
-
"postgres_changes",
|
|
1133
|
-
{
|
|
1134
|
-
event: "*",
|
|
1135
|
-
schema,
|
|
1136
|
-
table: workspaceMetricsBaseTable,
|
|
1137
|
-
filter: `workspace_id=eq.${workspaceId}`
|
|
1138
|
-
},
|
|
1139
|
-
async (payload) => {
|
|
1140
|
-
const payloadData = payload.new;
|
|
1141
|
-
console.log("Received workspace_metrics update:", {
|
|
1142
|
-
payload,
|
|
1143
|
-
payloadDate: payloadData?.date,
|
|
1144
|
-
payloadShift: payloadData?.shift_id,
|
|
1145
|
-
currentDate: operationalDate,
|
|
1146
|
-
currentShift: queryShiftId,
|
|
1147
|
-
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
1148
|
-
});
|
|
1149
|
-
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
1150
|
-
queueUpdate();
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
).subscribe((status) => {
|
|
1154
|
-
console.log(`Workspace metrics subscription status:`, status);
|
|
1155
|
-
});
|
|
1156
|
-
const workspaceActionsChannel = supabase.channel(`workspace-actions-${workspaceId}`).on(
|
|
1157
|
-
"postgres_changes",
|
|
1158
|
-
{
|
|
1159
|
-
event: "*",
|
|
1160
|
-
schema,
|
|
1161
|
-
table: workspaceActionsTable,
|
|
1162
|
-
filter: `workspace_id=eq.${workspaceId}`
|
|
1163
|
-
},
|
|
1164
|
-
async (payload) => {
|
|
1165
|
-
const payloadData = payload.new;
|
|
1166
|
-
console.log("Received workspace_actions update:", {
|
|
1167
|
-
payload,
|
|
1168
|
-
payloadDate: payloadData?.date,
|
|
1169
|
-
payloadShift: payloadData?.shift_id,
|
|
1170
|
-
currentDate: operationalDate,
|
|
1171
|
-
currentShift: queryShiftId,
|
|
1172
|
-
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
1173
|
-
});
|
|
1174
|
-
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
1175
|
-
queueUpdate();
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
).subscribe((status) => {
|
|
1179
|
-
console.log(`Workspace actions subscription status:`, status);
|
|
1180
|
-
});
|
|
1181
|
-
channels.push(metricsChannel, workspaceMetricsChannel, workspaceActionsChannel);
|
|
1182
|
-
fetchMetrics();
|
|
1183
|
-
setupSubscription();
|
|
1184
|
-
return () => {
|
|
1185
|
-
if (timeoutRef.current) {
|
|
1186
|
-
clearTimeout(timeoutRef.current);
|
|
1187
|
-
}
|
|
1188
|
-
channels.forEach((channel) => {
|
|
1189
|
-
console.log("Cleaning up channel subscription");
|
|
1190
|
-
supabase.removeChannel(channel);
|
|
1191
|
-
});
|
|
1192
|
-
if (channelRef.current) {
|
|
1193
|
-
supabase.removeChannel(channelRef.current);
|
|
1194
|
-
}
|
|
1195
|
-
};
|
|
1196
|
-
}, [supabase, workspaceId, date, shiftId, fetchMetrics, queueUpdate, setupSubscription, metricsTable, workspaceMetricsBaseTable, workspaceActionsTable, defaultTimezone, shiftConfig, schema, metricsTablePrefix]);
|
|
1197
|
-
return {
|
|
1198
|
-
metrics: metrics2,
|
|
1199
|
-
isLoading,
|
|
1200
|
-
error,
|
|
1201
|
-
refetch: fetchMetrics
|
|
1202
|
-
};
|
|
1203
|
-
};
|
|
1204
|
-
var useLineWorkspaceMetrics = (lineId, options) => {
|
|
1205
|
-
const entityConfig = useEntityConfig();
|
|
1206
|
-
const databaseConfig = useDatabaseConfig();
|
|
1207
|
-
const dateTimeConfig = useDateTimeConfig();
|
|
1208
|
-
const shiftConfig = useShiftConfig();
|
|
1209
|
-
const supabase = useSupabase();
|
|
1210
|
-
const [workspaces, setWorkspaces] = useState([]);
|
|
1211
|
-
const [loading, setLoading] = useState(true);
|
|
1212
|
-
const [error, setError] = useState(null);
|
|
1213
|
-
const [initialized, setInitialized] = useState(false);
|
|
1214
|
-
const queryShiftId = useMemo(() => {
|
|
1215
|
-
const currentShift = getCurrentShift(
|
|
1216
|
-
dateTimeConfig.defaultTimezone || "Asia/Kolkata",
|
|
1217
|
-
shiftConfig
|
|
1218
|
-
);
|
|
1219
|
-
return options?.initialShiftId !== void 0 ? options.initialShiftId : currentShift.shiftId;
|
|
1220
|
-
}, [options?.initialShiftId, dateTimeConfig.defaultTimezone, shiftConfig]);
|
|
1221
|
-
const queryDate = useMemo(() => {
|
|
1222
|
-
return options?.initialDate || getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
1223
|
-
}, [options?.initialDate, dateTimeConfig.defaultTimezone]);
|
|
1224
|
-
const metricsTable = useMemo(() => {
|
|
1225
|
-
const companyId = entityConfig.companyId;
|
|
1226
|
-
if (!companyId) return "";
|
|
1227
|
-
const metricsTablePrefix = getMetricsTablePrefix();
|
|
1228
|
-
return `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
1229
|
-
}, [entityConfig.companyId]);
|
|
1230
|
-
const schema = databaseConfig.schema ?? "public";
|
|
1231
|
-
const fetchWorkspaceMetrics = useCallback(async () => {
|
|
1232
|
-
if (!lineId) return;
|
|
1233
|
-
if (!initialized) {
|
|
1234
|
-
setLoading(true);
|
|
1235
|
-
}
|
|
1236
|
-
setError(null);
|
|
1237
|
-
try {
|
|
1238
|
-
console.log("Fetching workspace metrics with params:", {
|
|
1239
|
-
lineId,
|
|
1240
|
-
queryDate,
|
|
1241
|
-
queryShiftId,
|
|
1242
|
-
metricsTable
|
|
1243
|
-
});
|
|
1244
|
-
const { data, error: fetchError } = await supabase.from(metricsTable).select(`
|
|
1245
|
-
workspace_name,
|
|
1246
|
-
total_output,
|
|
1247
|
-
avg_pph,
|
|
1248
|
-
efficiency,
|
|
1249
|
-
workspace_id,
|
|
1250
|
-
avg_cycle_time,
|
|
1251
|
-
performance_score,
|
|
1252
|
-
trend_score,
|
|
1253
|
-
line_id,
|
|
1254
|
-
total_day_output
|
|
1255
|
-
`).eq("date", queryDate).eq("shift_id", queryShiftId).eq("line_id", lineId).order("workspace_name", { ascending: true });
|
|
1256
|
-
if (fetchError) throw fetchError;
|
|
1257
|
-
const transformedData = (data || []).map((item) => ({
|
|
1258
|
-
company_id: entityConfig.companyId || "unknown",
|
|
1259
|
-
line_id: item.line_id,
|
|
1260
|
-
shift_id: queryShiftId,
|
|
1261
|
-
date: queryDate,
|
|
1262
|
-
workspace_uuid: item.workspace_id,
|
|
1263
|
-
workspace_name: item.workspace_name,
|
|
1264
|
-
action_count: item.total_output || 0,
|
|
1265
|
-
pph: item.avg_pph || 0,
|
|
1266
|
-
performance_score: item.performance_score || 0,
|
|
1267
|
-
avg_cycle_time: item.avg_cycle_time || 0,
|
|
1268
|
-
trend: item.trend_score === 1 ? 2 : 0,
|
|
1269
|
-
predicted_output: 0,
|
|
1270
|
-
efficiency: item.efficiency || 0,
|
|
1271
|
-
action_threshold: item.total_day_output || 0
|
|
1272
|
-
}));
|
|
1273
|
-
setWorkspaces(transformedData);
|
|
1274
|
-
setInitialized(true);
|
|
1275
|
-
} catch (err) {
|
|
1276
|
-
console.error("Error fetching workspace metrics:", err);
|
|
1277
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
1278
|
-
} finally {
|
|
1279
|
-
setLoading(false);
|
|
1280
|
-
}
|
|
1281
|
-
}, [lineId, queryDate, queryShiftId, metricsTable, supabase, entityConfig.companyId]);
|
|
1282
|
-
useEffect(() => {
|
|
1283
|
-
if (!initialized) {
|
|
1284
|
-
fetchWorkspaceMetrics();
|
|
626
|
+
const { data, error } = await query;
|
|
627
|
+
if (error) {
|
|
628
|
+
console.error("Error in getWorkspacesData:", error);
|
|
629
|
+
throw error;
|
|
1285
630
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
};
|
|
1305
|
-
const channel = setupSubscription();
|
|
1306
|
-
return () => {
|
|
1307
|
-
if (channel) {
|
|
1308
|
-
supabase.removeChannel(channel);
|
|
1309
|
-
}
|
|
1310
|
-
};
|
|
1311
|
-
}, [lineId, queryDate, queryShiftId, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema]);
|
|
1312
|
-
useEffect(() => {
|
|
1313
|
-
setInitialized(false);
|
|
1314
|
-
}, [lineId, queryDate, queryShiftId]);
|
|
1315
|
-
const refreshWorkspaces = fetchWorkspaceMetrics;
|
|
1316
|
-
return useMemo(
|
|
1317
|
-
() => ({ workspaces, loading, error, refreshWorkspaces }),
|
|
1318
|
-
[workspaces, loading, error, refreshWorkspaces]
|
|
1319
|
-
);
|
|
1320
|
-
};
|
|
1321
|
-
|
|
1322
|
-
// src/lib/services/dashboardService.ts
|
|
1323
|
-
var getTable = (dbConfig, tableName) => {
|
|
1324
|
-
const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
|
|
1325
|
-
const userValue = dbConfig?.tables?.[tableName];
|
|
1326
|
-
return userValue ?? defaults2[tableName];
|
|
1327
|
-
};
|
|
1328
|
-
var dashboardService = {
|
|
1329
|
-
// Example for getLineInfo:
|
|
1330
|
-
async getLineInfo(lineIdInput) {
|
|
631
|
+
return (data || []).map((item) => ({
|
|
632
|
+
company_id: item.company_id,
|
|
633
|
+
line_id: item.line_id,
|
|
634
|
+
shift_id: item.shift_id,
|
|
635
|
+
date: item.date,
|
|
636
|
+
workspace_uuid: item.workspace_id,
|
|
637
|
+
workspace_name: item.workspace_name,
|
|
638
|
+
action_count: item.total_output || 0,
|
|
639
|
+
pph: item.avg_pph || 0,
|
|
640
|
+
performance_score: item.performance_score || 0,
|
|
641
|
+
avg_cycle_time: item.avg_cycle_time || 0,
|
|
642
|
+
trend: item.trend_score === 1 ? 2 : item.trend_score === 0 ? 0 : 1,
|
|
643
|
+
predicted_output: item.ideal_output || 0,
|
|
644
|
+
efficiency: item.efficiency || 0,
|
|
645
|
+
action_threshold: item.total_day_output || 0
|
|
646
|
+
}));
|
|
647
|
+
},
|
|
648
|
+
async getWorkspaceDetailedMetrics(workspaceUuid, dateProp, shiftIdProp) {
|
|
1331
649
|
const supabase = _getSupabaseInstance();
|
|
1332
650
|
const config = _getDashboardConfigInstance();
|
|
1333
|
-
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
1334
651
|
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
1335
652
|
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
1336
653
|
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
1337
654
|
const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
|
|
1338
|
-
const linesTable = getTable(dbConfig, "lines");
|
|
1339
|
-
const lineMetricsTable = getTable(dbConfig, "lineMetrics");
|
|
1340
655
|
const companyId = entityConfig.companyId;
|
|
656
|
+
if (!companyId) {
|
|
657
|
+
throw new Error("Company ID must be configured for detailed workspace metrics.");
|
|
658
|
+
}
|
|
1341
659
|
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
1342
|
-
const metricsTable = `${metricsTablePrefixStr}_${companyId
|
|
1343
|
-
const defaultLineId = entityConfig.defaultLineId;
|
|
1344
|
-
const secondaryLineId = entityConfig.secondaryLineId;
|
|
1345
|
-
const factoryViewId = entityConfig.factoryViewId ?? "factory";
|
|
1346
|
-
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
1347
|
-
const { shiftId, date } = getCurrentShift(defaultTimezone, shiftConfig);
|
|
1348
|
-
const lineId = lineIdInput;
|
|
1349
|
-
if (lineId === factoryViewId) {
|
|
1350
|
-
if (!defaultLineId || !secondaryLineId || !companyId) {
|
|
1351
|
-
throw new Error("Factory View requires defaultLineId, secondaryLineId, and companyId to be configured.");
|
|
1352
|
-
}
|
|
1353
|
-
const { data: lineData2, error: lineError2 } = await supabase.from(linesTable).select(`
|
|
1354
|
-
id,
|
|
1355
|
-
line_name,
|
|
1356
|
-
factory_id,
|
|
1357
|
-
factories!lines_factory_id_fkey(factory_name),
|
|
1358
|
-
company_id,
|
|
1359
|
-
companies!lines_company_id_fkey(company_name:name)
|
|
1360
|
-
`).eq("id", defaultLineId).maybeSingle();
|
|
1361
|
-
if (lineError2) throw lineError2;
|
|
1362
|
-
if (!lineData2) throw new Error(`Configured default line (${defaultLineId}) not found`);
|
|
1363
|
-
const { data: metricsData, error: metricsError } = await supabase.from(lineMetricsTable).select("*").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
|
|
1364
|
-
if (metricsError) throw metricsError;
|
|
1365
|
-
const { data: performanceData2, error: performanceError2 } = await supabase.from(metricsTable).select("efficiency").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
|
|
1366
|
-
if (performanceError2) throw performanceError2;
|
|
1367
|
-
const underperformingCount2 = performanceData2?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
|
|
1368
|
-
const totalValidWorkspaces2 = performanceData2?.filter((w) => w.efficiency >= 10).length || 0;
|
|
1369
|
-
const combinedMetrics = (metricsData || []).reduce((acc, m) => {
|
|
1370
|
-
acc.avg_efficiency += m.efficiency ?? 0;
|
|
1371
|
-
acc.current_output += m.current_output ?? 0;
|
|
1372
|
-
acc.ideal_output += m.ideal_output ?? m.line_threshold ?? 0;
|
|
1373
|
-
return acc;
|
|
1374
|
-
}, { avg_efficiency: 0, current_output: 0, ideal_output: 0 });
|
|
1375
|
-
metricsData?.length || 1;
|
|
1376
|
-
return {
|
|
1377
|
-
line_id: factoryViewId,
|
|
1378
|
-
// Use configured factory view ID
|
|
1379
|
-
line_name: "Factory View",
|
|
1380
|
-
// Consider making this configurable?
|
|
1381
|
-
company_id: lineData2.company_id,
|
|
1382
|
-
company_name: lineData2.companies?.[0]?.company_name ?? "",
|
|
1383
|
-
factory_id: lineData2.factory_id,
|
|
1384
|
-
factory_name: lineData2.factories?.[0]?.factory_name ?? "",
|
|
1385
|
-
shift_id: shiftId,
|
|
1386
|
-
date,
|
|
1387
|
-
metrics: {
|
|
1388
|
-
avg_efficiency: (performanceData2?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces2 || 1),
|
|
1389
|
-
// Use performance data for avg efficiency
|
|
1390
|
-
avg_cycle_time: 0,
|
|
1391
|
-
// Needs calculation logic if required for factory view
|
|
1392
|
-
current_output: combinedMetrics.current_output,
|
|
1393
|
-
ideal_output: combinedMetrics.ideal_output,
|
|
1394
|
-
total_workspaces: 44,
|
|
1395
|
-
// SRC ALIGNMENT: Use hardcoded 44 for factory view total_workspaces
|
|
1396
|
-
underperforming_workspaces: underperformingCount2,
|
|
1397
|
-
underperforming_workspace_names: [],
|
|
1398
|
-
// Populate if needed
|
|
1399
|
-
underperforming_workspace_uuids: [],
|
|
1400
|
-
// Populate if needed
|
|
1401
|
-
output_array: [],
|
|
1402
|
-
// Combine if needed
|
|
1403
|
-
line_threshold: combinedMetrics.ideal_output,
|
|
1404
|
-
threshold_pph: 0,
|
|
1405
|
-
// Needs calculation logic if required
|
|
1406
|
-
shift_start: shiftConfig.dayShift?.startTime || "06:00",
|
|
1407
|
-
// Use config
|
|
1408
|
-
shift_end: shiftConfig.dayShift?.endTime || "18:00",
|
|
1409
|
-
// Use config
|
|
1410
|
-
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1411
|
-
poorest_performing_workspaces: []
|
|
1412
|
-
// Populate if needed
|
|
1413
|
-
}
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
if (!companyId) {
|
|
1417
|
-
throw new Error("Company ID must be configured for individual line requests.");
|
|
1418
|
-
}
|
|
1419
|
-
const { data: lineData, error: lineError } = await supabase.from(linesTable).select("id, line_name, factory_id, factories!lines_factory_id_fkey(factory_name), company_id, companies!lines_company_id_fkey(company_name:name)").eq("id", lineId).maybeSingle();
|
|
1420
|
-
if (lineError) throw lineError;
|
|
1421
|
-
if (!lineData) throw new Error(`Line with ID ${lineId} not found`);
|
|
1422
|
-
let metricsFromDb = null;
|
|
1423
|
-
try {
|
|
1424
|
-
const { data: fetchedMetrics, error } = await supabase.from(lineMetricsTable).select("*").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date).maybeSingle();
|
|
1425
|
-
if (error) throw error;
|
|
1426
|
-
metricsFromDb = fetchedMetrics;
|
|
1427
|
-
} catch (err) {
|
|
1428
|
-
console.error(`Error fetching line metrics for ${lineId}:`, err);
|
|
1429
|
-
}
|
|
1430
|
-
const { data: performanceData, error: performanceError } = await supabase.from(metricsTable).select("efficiency").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date);
|
|
1431
|
-
if (performanceError) throw performanceError;
|
|
1432
|
-
const underperformingCount = performanceData?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
|
|
1433
|
-
const totalValidWorkspaces = performanceData?.filter((w) => w.efficiency >= 10).length || 0;
|
|
1434
|
-
const avgEfficiencyFromPerf = (performanceData?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces || 1);
|
|
1435
|
-
const useFallbackMetrics = !metricsFromDb;
|
|
1436
|
-
let metricsForReturn;
|
|
1437
|
-
if (useFallbackMetrics) {
|
|
1438
|
-
metricsForReturn = {
|
|
1439
|
-
avg_efficiency: 0,
|
|
1440
|
-
avg_cycle_time: 0,
|
|
1441
|
-
current_output: 0,
|
|
1442
|
-
ideal_output: 0,
|
|
1443
|
-
total_workspaces: 42,
|
|
1444
|
-
underperforming_workspaces: underperformingCount,
|
|
1445
|
-
underperforming_workspace_names: [],
|
|
1446
|
-
underperforming_workspace_uuids: [],
|
|
1447
|
-
output_array: [],
|
|
1448
|
-
line_threshold: 0,
|
|
1449
|
-
threshold_pph: 0,
|
|
1450
|
-
shift_start: "06:00",
|
|
1451
|
-
shift_end: "14:00",
|
|
1452
|
-
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1453
|
-
poorest_performing_workspaces: []
|
|
1454
|
-
};
|
|
1455
|
-
} else {
|
|
1456
|
-
metricsForReturn = {
|
|
1457
|
-
avg_efficiency: metricsFromDb.efficiency ?? avgEfficiencyFromPerf,
|
|
1458
|
-
avg_cycle_time: metricsFromDb.avg_cycle_time || 0,
|
|
1459
|
-
current_output: metricsFromDb.current_output || 0,
|
|
1460
|
-
ideal_output: metricsFromDb.ideal_output || metricsFromDb.line_threshold || 0,
|
|
1461
|
-
total_workspaces: metricsFromDb.total_workspaces ?? workspaceConfig.totalWorkspaces ?? 42,
|
|
1462
|
-
underperforming_workspaces: underperformingCount,
|
|
1463
|
-
underperforming_workspace_names: metricsFromDb.underperforming_workspace_names || [],
|
|
1464
|
-
underperforming_workspace_uuids: metricsFromDb.underperforming_workspace_uuids || [],
|
|
1465
|
-
output_array: metricsFromDb.output_array || [],
|
|
1466
|
-
line_threshold: metricsFromDb.line_threshold || 0,
|
|
1467
|
-
threshold_pph: metricsFromDb.threshold_pph || 0,
|
|
1468
|
-
shift_start: metricsFromDb.shift_start || shiftConfig.dayShift?.startTime || "06:00",
|
|
1469
|
-
shift_end: metricsFromDb.shift_end || shiftConfig.dayShift?.endTime || "18:00",
|
|
1470
|
-
last_updated: metricsFromDb.last_updated || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1471
|
-
poorest_performing_workspaces: metricsFromDb.poorest_performing_workspaces || []
|
|
1472
|
-
};
|
|
1473
|
-
if (metricsFromDb.efficiency === null || metricsFromDb.efficiency === void 0) {
|
|
1474
|
-
metricsForReturn.avg_efficiency = avgEfficiencyFromPerf;
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
return {
|
|
1478
|
-
line_id: lineData.id,
|
|
1479
|
-
line_name: lineData.line_name,
|
|
1480
|
-
company_id: lineData.company_id,
|
|
1481
|
-
company_name: lineData.companies?.[0]?.company_name ?? "",
|
|
1482
|
-
factory_id: lineData.factory_id,
|
|
1483
|
-
factory_name: lineData.factories?.[0]?.factory_name ?? "",
|
|
1484
|
-
shift_id: shiftId,
|
|
1485
|
-
date,
|
|
1486
|
-
metrics: metricsForReturn
|
|
1487
|
-
};
|
|
1488
|
-
},
|
|
1489
|
-
async getWorkspacesData(lineIdInput, dateProp, shiftProp) {
|
|
1490
|
-
const supabase = _getSupabaseInstance();
|
|
1491
|
-
const config = _getDashboardConfigInstance();
|
|
1492
|
-
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
1493
|
-
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
1494
|
-
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
1495
|
-
const companyId = entityConfig.companyId;
|
|
1496
|
-
if (!companyId) {
|
|
1497
|
-
throw new Error("Company ID must be configured for workspace data requests.");
|
|
1498
|
-
}
|
|
1499
|
-
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
1500
|
-
const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
|
|
1501
|
-
const defaultLineId = entityConfig.defaultLineId;
|
|
1502
|
-
const secondaryLineId = entityConfig.secondaryLineId;
|
|
1503
|
-
const factoryViewId = entityConfig.factoryViewId ?? "factory";
|
|
1504
|
-
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
1505
|
-
const currentShiftResult = getCurrentShift(defaultTimezone, shiftConfig);
|
|
1506
|
-
const queryDate = dateProp || getOperationalDate(defaultTimezone);
|
|
1507
|
-
const queryShiftId = shiftProp ?? currentShiftResult.shiftId;
|
|
1508
|
-
const lineId = lineIdInput;
|
|
1509
|
-
let query = supabase.from(metricsTable).select("company_id,line_id,shift_id,date,workspace_id,workspace_name,total_output,avg_pph,performance_score,avg_cycle_time,trend_score,ideal_output,efficiency,total_day_output").eq("shift_id", queryShiftId).eq("date", queryDate);
|
|
1510
|
-
if (!lineId || lineId === factoryViewId) {
|
|
1511
|
-
if (!defaultLineId || !secondaryLineId) {
|
|
1512
|
-
throw new Error("Factory View requires defaultLineId and secondaryLineId to be configured for workspace data.");
|
|
1513
|
-
}
|
|
1514
|
-
query = query.in("line_id", [defaultLineId, secondaryLineId]);
|
|
1515
|
-
} else {
|
|
1516
|
-
query = query.eq("line_id", lineId);
|
|
1517
|
-
}
|
|
1518
|
-
const { data, error } = await query;
|
|
1519
|
-
if (error) {
|
|
1520
|
-
console.error("Error in getWorkspacesData:", error);
|
|
1521
|
-
throw error;
|
|
1522
|
-
}
|
|
1523
|
-
return (data || []).map((item) => ({
|
|
1524
|
-
company_id: item.company_id,
|
|
1525
|
-
line_id: item.line_id,
|
|
1526
|
-
shift_id: item.shift_id,
|
|
1527
|
-
date: item.date,
|
|
1528
|
-
workspace_uuid: item.workspace_id,
|
|
1529
|
-
workspace_name: item.workspace_name,
|
|
1530
|
-
action_count: item.total_output || 0,
|
|
1531
|
-
pph: item.avg_pph || 0,
|
|
1532
|
-
performance_score: item.performance_score || 0,
|
|
1533
|
-
avg_cycle_time: item.avg_cycle_time || 0,
|
|
1534
|
-
trend: item.trend_score === 1 ? 2 : item.trend_score === 0 ? 0 : 1,
|
|
1535
|
-
predicted_output: item.ideal_output || 0,
|
|
1536
|
-
efficiency: item.efficiency || 0,
|
|
1537
|
-
action_threshold: item.total_day_output || 0
|
|
1538
|
-
}));
|
|
1539
|
-
},
|
|
1540
|
-
async getWorkspaceDetailedMetrics(workspaceUuid, dateProp, shiftIdProp) {
|
|
1541
|
-
const supabase = _getSupabaseInstance();
|
|
1542
|
-
const config = _getDashboardConfigInstance();
|
|
1543
|
-
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
1544
|
-
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
1545
|
-
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
1546
|
-
const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
|
|
1547
|
-
const companyId = entityConfig.companyId;
|
|
1548
|
-
if (!companyId) {
|
|
1549
|
-
throw new Error("Company ID must be configured for detailed workspace metrics.");
|
|
1550
|
-
}
|
|
1551
|
-
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
1552
|
-
const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
|
|
660
|
+
const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
|
|
1553
661
|
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
1554
662
|
const currentShiftResult = getCurrentShift(defaultTimezone, shiftConfig);
|
|
1555
663
|
const queryDate = dateProp || getOperationalDate(defaultTimezone);
|
|
@@ -1689,7 +797,7 @@ var dashboardService = {
|
|
|
1689
797
|
const supabase = _getSupabaseInstance();
|
|
1690
798
|
const config = _getDashboardConfigInstance();
|
|
1691
799
|
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
1692
|
-
const linesTable =
|
|
800
|
+
const linesTable = getTable2(dbConfig, "lines");
|
|
1693
801
|
const companyId = config.entityConfig?.companyId;
|
|
1694
802
|
try {
|
|
1695
803
|
let query = supabase.from(linesTable).select(`
|
|
@@ -1731,8 +839,8 @@ var dashboardService = {
|
|
|
1731
839
|
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
1732
840
|
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
1733
841
|
const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
|
|
1734
|
-
const linesTable =
|
|
1735
|
-
const lineMetricsTable =
|
|
842
|
+
const linesTable = getTable2(dbConfig, "lines");
|
|
843
|
+
const lineMetricsTable = getTable2(dbConfig, "lineMetrics");
|
|
1736
844
|
const companyId = entityConfig.companyId;
|
|
1737
845
|
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
1738
846
|
const metricsTable = `${metricsTablePrefixStr}_${companyId ? companyId.replace(/-/g, "_") : "unknown_company"}`;
|
|
@@ -1883,7 +991,7 @@ var dashboardService = {
|
|
|
1883
991
|
const formattedStartDate = formatDate(startDate);
|
|
1884
992
|
const formattedEndDate = formatDate(endDate);
|
|
1885
993
|
try {
|
|
1886
|
-
const { data, error } = await supabase.from(metricsTable).select("date, shift_id, efficiency, total_output, avg_cycle_time, ideal_output, avg_pph, pph_threshold, workspace_rank").eq("workspace_id", workspaceUuid).gte("date", formattedStartDate).lte("date", formattedEndDate).order("date", { ascending: true }).order("shift_id", { ascending: true });
|
|
994
|
+
const { data, error } = await supabase.from(metricsTable).select("date, shift_id, efficiency, total_output, avg_cycle_time, idle_time, ideal_output, avg_pph, pph_threshold, workspace_rank").eq("workspace_id", workspaceUuid).gte("date", formattedStartDate).lte("date", formattedEndDate).order("date", { ascending: true }).order("shift_id", { ascending: true });
|
|
1887
995
|
if (error) throw error;
|
|
1888
996
|
if (!data) return [];
|
|
1889
997
|
const transformedData = data.map((item) => ({
|
|
@@ -1896,7 +1004,8 @@ var dashboardService = {
|
|
|
1896
1004
|
ideal_output: item.ideal_output || 0,
|
|
1897
1005
|
avg_pph: item.avg_pph || 0,
|
|
1898
1006
|
pph_threshold: item.pph_threshold || 0,
|
|
1899
|
-
workspace_rank: item.workspace_rank || 0
|
|
1007
|
+
workspace_rank: item.workspace_rank || 0,
|
|
1008
|
+
idle_time: item.idle_time || 0
|
|
1900
1009
|
}));
|
|
1901
1010
|
return transformedData;
|
|
1902
1011
|
} catch (err) {
|
|
@@ -1909,7 +1018,7 @@ var dashboardService = {
|
|
|
1909
1018
|
const config = _getDashboardConfigInstance();
|
|
1910
1019
|
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
1911
1020
|
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
1912
|
-
const lineMetricsTable =
|
|
1021
|
+
const lineMetricsTable = getTable2(dbConfig, "lineMetrics");
|
|
1913
1022
|
const defaultLineId = entityConfig.defaultLineId;
|
|
1914
1023
|
const secondaryLineId = entityConfig.secondaryLineId;
|
|
1915
1024
|
const factoryViewId = entityConfig.factoryViewId ?? "factory";
|
|
@@ -1996,162 +1105,35 @@ var dashboardService = {
|
|
|
1996
1105
|
}
|
|
1997
1106
|
};
|
|
1998
1107
|
|
|
1999
|
-
// src/lib/
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
...fetchedData,
|
|
2029
|
-
hourly_action_counts: initialHourlyCounts
|
|
2030
|
-
});
|
|
2031
|
-
setIsLoading(false);
|
|
2032
|
-
if (fetchedData.hourly_action_counts && fetchedData.hourly_action_counts.length > 0) {
|
|
2033
|
-
const totalSteps = 60;
|
|
2034
|
-
let currentStep = 0;
|
|
2035
|
-
let animationIntervalId = setInterval(() => {
|
|
2036
|
-
currentStep++;
|
|
2037
|
-
const progress6 = currentStep / totalSteps;
|
|
2038
|
-
setMetrics((prevMetrics) => {
|
|
2039
|
-
const currentHourlyData = (fetchedData.hourly_action_counts || []).map(
|
|
2040
|
-
(target) => Math.round(target * progress6)
|
|
2041
|
-
);
|
|
2042
|
-
return {
|
|
2043
|
-
...fetchedData,
|
|
2044
|
-
// Base with all other correct data from the latest fetch
|
|
2045
|
-
hourly_action_counts: currentHourlyData
|
|
2046
|
-
};
|
|
2047
|
-
});
|
|
2048
|
-
if (currentStep >= totalSteps) {
|
|
2049
|
-
if (animationIntervalId) clearInterval(animationIntervalId);
|
|
2050
|
-
setMetrics(fetchedData);
|
|
2051
|
-
}
|
|
2052
|
-
}, 1e3 / 60);
|
|
2053
|
-
} else {
|
|
2054
|
-
setMetrics(fetchedData);
|
|
2055
|
-
}
|
|
2056
|
-
} catch (err) {
|
|
2057
|
-
console.error("Error fetching historic workspace metrics:", err);
|
|
2058
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
2059
|
-
setIsLoading(false);
|
|
2060
|
-
setMetrics(null);
|
|
2061
|
-
}
|
|
2062
|
-
}, [workspaceId, date, inputShiftId]);
|
|
2063
|
-
useEffect(() => {
|
|
2064
|
-
fetchAndAnimateMetrics();
|
|
2065
|
-
}, [fetchAndAnimateMetrics]);
|
|
2066
|
-
const refetch = useCallback(async () => {
|
|
2067
|
-
if (!workspaceId || !date || inputShiftId === void 0) {
|
|
2068
|
-
setError(null);
|
|
2069
|
-
return;
|
|
2070
|
-
}
|
|
2071
|
-
setIsLoading(true);
|
|
2072
|
-
setError(null);
|
|
2073
|
-
try {
|
|
2074
|
-
const fetchedData = await dashboardService.getWorkspaceDetailedMetrics(
|
|
2075
|
-
workspaceId,
|
|
2076
|
-
date,
|
|
2077
|
-
inputShiftId
|
|
2078
|
-
);
|
|
2079
|
-
if (!fetchedData) {
|
|
2080
|
-
setMetrics(null);
|
|
2081
|
-
return;
|
|
2082
|
-
}
|
|
2083
|
-
setMetrics(fetchedData);
|
|
2084
|
-
} catch (err) {
|
|
2085
|
-
console.error("Error re-fetching historic workspace metrics:", err);
|
|
2086
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
2087
|
-
setMetrics(null);
|
|
2088
|
-
} finally {
|
|
2089
|
-
setIsLoading(false);
|
|
2090
|
-
}
|
|
2091
|
-
}, [workspaceId, date, inputShiftId]);
|
|
2092
|
-
return {
|
|
2093
|
-
metrics: metrics2,
|
|
2094
|
-
isLoading,
|
|
2095
|
-
error,
|
|
2096
|
-
refetch
|
|
2097
|
-
};
|
|
2098
|
-
};
|
|
2099
|
-
|
|
2100
|
-
// src/lib/services/actionService.ts
|
|
2101
|
-
var getTable2 = (dbConfig, tableName) => {
|
|
2102
|
-
const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
|
|
2103
|
-
const userValue = dbConfig?.tables?.[tableName];
|
|
2104
|
-
return userValue ?? defaults2[tableName];
|
|
2105
|
-
};
|
|
2106
|
-
var actionService = {
|
|
2107
|
-
async getActionsByName(actionNames, companyIdInput) {
|
|
2108
|
-
const supabase = _getSupabaseInstance();
|
|
2109
|
-
const config = _getDashboardConfigInstance();
|
|
2110
|
-
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
2111
|
-
const entityConfig = config.entityConfig;
|
|
2112
|
-
const actionsTable = getTable2(dbConfig, "actions");
|
|
2113
|
-
const targetCompanyId = companyIdInput ?? entityConfig?.companyId;
|
|
2114
|
-
if (!targetCompanyId) {
|
|
2115
|
-
throw new Error("Company ID must be provided either via entityConfig.companyId or as an argument to getActionsByName.");
|
|
2116
|
-
}
|
|
2117
|
-
const { data, error } = await supabase.from(actionsTable).select("id, action_name, company_id").eq("company_id", targetCompanyId).in("action_name", actionNames);
|
|
2118
|
-
if (error) {
|
|
2119
|
-
console.error(`Error fetching actions from table ${actionsTable}:`, error);
|
|
2120
|
-
throw error;
|
|
2121
|
-
}
|
|
2122
|
-
return data || [];
|
|
2123
|
-
}
|
|
2124
|
-
};
|
|
2125
|
-
|
|
2126
|
-
// src/lib/services/realtimeService.ts
|
|
2127
|
-
function isValidLineInfoPayload(payload) {
|
|
2128
|
-
return payload && typeof payload === "object" && "line_id" in payload;
|
|
2129
|
-
}
|
|
2130
|
-
function isValidWorkspaceMetricsPayload(payload) {
|
|
2131
|
-
return payload && typeof payload === "object" && "workspace_uuid" in payload;
|
|
2132
|
-
}
|
|
2133
|
-
function isValidWorkspaceDetailedMetricsPayload(payload) {
|
|
2134
|
-
return payload && typeof payload === "object" && "workspace_id" in payload && "hourly_action_counts" in payload;
|
|
2135
|
-
}
|
|
2136
|
-
var realtimeService = {
|
|
2137
|
-
subscribeToLineInfo(lineId, shiftId, date, onDataUpdate) {
|
|
2138
|
-
const supabase = _getSupabaseInstance();
|
|
2139
|
-
const config = _getDashboardConfigInstance();
|
|
2140
|
-
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
2141
|
-
const schema = dbConfig.schema ?? "public";
|
|
2142
|
-
const channelName = `line_info_${lineId}_${shiftId}_${date}`;
|
|
2143
|
-
const TABLE_NAME = "line_info";
|
|
2144
|
-
const channel = supabase.channel(channelName);
|
|
2145
|
-
channel.on(
|
|
2146
|
-
"postgres_changes",
|
|
2147
|
-
{ event: "*", schema, table: TABLE_NAME, filter: `line_id=eq.${lineId}` },
|
|
2148
|
-
(payload) => {
|
|
2149
|
-
const record = payload.new ?? payload.old;
|
|
2150
|
-
if (record && isValidLineInfoPayload(record)) {
|
|
2151
|
-
if ((!record.shift_id || record.shift_id === shiftId) && (!record.date || record.date === date)) {
|
|
2152
|
-
onDataUpdate(record);
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
1108
|
+
// src/lib/services/realtimeService.ts
|
|
1109
|
+
function isValidLineInfoPayload(payload) {
|
|
1110
|
+
return payload && typeof payload === "object" && "line_id" in payload;
|
|
1111
|
+
}
|
|
1112
|
+
function isValidWorkspaceMetricsPayload(payload) {
|
|
1113
|
+
return payload && typeof payload === "object" && "workspace_uuid" in payload;
|
|
1114
|
+
}
|
|
1115
|
+
function isValidWorkspaceDetailedMetricsPayload(payload) {
|
|
1116
|
+
return payload && typeof payload === "object" && "workspace_id" in payload && "hourly_action_counts" in payload;
|
|
1117
|
+
}
|
|
1118
|
+
var realtimeService = {
|
|
1119
|
+
subscribeToLineInfo(lineId, shiftId, date, onDataUpdate) {
|
|
1120
|
+
const supabase = _getSupabaseInstance();
|
|
1121
|
+
const config = _getDashboardConfigInstance();
|
|
1122
|
+
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
1123
|
+
const schema = dbConfig.schema ?? "public";
|
|
1124
|
+
const channelName = `line_info_${lineId}_${shiftId}_${date}`;
|
|
1125
|
+
const TABLE_NAME = "line_info";
|
|
1126
|
+
const channel = supabase.channel(channelName);
|
|
1127
|
+
channel.on(
|
|
1128
|
+
"postgres_changes",
|
|
1129
|
+
{ event: "*", schema, table: TABLE_NAME, filter: `line_id=eq.${lineId}` },
|
|
1130
|
+
(payload) => {
|
|
1131
|
+
const record = payload.new ?? payload.old;
|
|
1132
|
+
if (record && isValidLineInfoPayload(record)) {
|
|
1133
|
+
if ((!record.shift_id || record.shift_id === shiftId) && (!record.date || record.date === date)) {
|
|
1134
|
+
onDataUpdate(record);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
2155
1137
|
}
|
|
2156
1138
|
).subscribe((status, err) => {
|
|
2157
1139
|
if (err) console.error(`[RealtimeService] Line info subscription error for ${channelName}:`, err);
|
|
@@ -2824,6 +1806,7 @@ var authRateLimitService = {
|
|
|
2824
1806
|
clearAllRateLimits
|
|
2825
1807
|
};
|
|
2826
1808
|
var isMixpanelInitialized = false;
|
|
1809
|
+
var currentUserProperties;
|
|
2827
1810
|
var initializeCoreMixpanel = (token, debug, trackPageView) => {
|
|
2828
1811
|
if (!token) {
|
|
2829
1812
|
console.warn("Mixpanel token not provided for initialization. Mixpanel will not be enabled.");
|
|
@@ -2849,7 +1832,13 @@ var trackCorePageView = (pageName, properties) => {
|
|
|
2849
1832
|
};
|
|
2850
1833
|
var trackCoreEvent = (eventName, properties) => {
|
|
2851
1834
|
if (!isMixpanelInitialized) return;
|
|
2852
|
-
|
|
1835
|
+
const mergedProps = {
|
|
1836
|
+
// Precedence order: explicit properties passed by caller should override
|
|
1837
|
+
// automatically appended user properties to avoid accidental overwrites.
|
|
1838
|
+
...currentUserProperties || {},
|
|
1839
|
+
...properties || {}
|
|
1840
|
+
};
|
|
1841
|
+
mixpanel.track(eventName, mergedProps);
|
|
2853
1842
|
};
|
|
2854
1843
|
var identifyCoreUser = (userId, userProperties) => {
|
|
2855
1844
|
if (!isMixpanelInitialized) return;
|
|
@@ -2857,6 +1846,7 @@ var identifyCoreUser = (userId, userProperties) => {
|
|
|
2857
1846
|
if (userProperties) {
|
|
2858
1847
|
mixpanel.people.set(userProperties);
|
|
2859
1848
|
}
|
|
1849
|
+
currentUserProperties = { ...userProperties };
|
|
2860
1850
|
};
|
|
2861
1851
|
var resetCoreMixpanel = () => {
|
|
2862
1852
|
if (!isMixpanelInitialized) return;
|
|
@@ -2879,195 +1869,1239 @@ var SSEChatClient = class {
|
|
|
2879
1869
|
}
|
|
2880
1870
|
}
|
|
2881
1871
|
}
|
|
2882
|
-
const controller = new AbortController();
|
|
2883
|
-
this.controllers.set(connectionId, controller);
|
|
2884
|
-
console.log("[SSEClient] Sending message:", {
|
|
2885
|
-
message,
|
|
2886
|
-
thread_id: threadId,
|
|
2887
|
-
user_id: userId,
|
|
2888
|
-
context
|
|
1872
|
+
const controller = new AbortController();
|
|
1873
|
+
this.controllers.set(connectionId, controller);
|
|
1874
|
+
console.log("[SSEClient] Sending message:", {
|
|
1875
|
+
message,
|
|
1876
|
+
thread_id: threadId,
|
|
1877
|
+
user_id: userId,
|
|
1878
|
+
context
|
|
1879
|
+
});
|
|
1880
|
+
const agnoApiUrl = this.baseUrl || "https://optifye-agent-production.up.railway.app";
|
|
1881
|
+
const endpoint = `${agnoApiUrl}/api/chat`;
|
|
1882
|
+
console.log("[SSEClient] Posting directly to AGNO:", endpoint);
|
|
1883
|
+
const response = await fetch(endpoint, {
|
|
1884
|
+
method: "POST",
|
|
1885
|
+
headers: {
|
|
1886
|
+
"Content-Type": "application/json",
|
|
1887
|
+
"Accept": "text/event-stream"
|
|
1888
|
+
},
|
|
1889
|
+
body: JSON.stringify({
|
|
1890
|
+
message,
|
|
1891
|
+
thread_id: threadId,
|
|
1892
|
+
user_id: userId,
|
|
1893
|
+
company_id: context.companyId,
|
|
1894
|
+
line_id: context.lineId,
|
|
1895
|
+
shift_id: context.shiftId
|
|
1896
|
+
}),
|
|
1897
|
+
signal: controller.signal,
|
|
1898
|
+
// Don't include credentials since the API returns Access-Control-Allow-Origin: *
|
|
1899
|
+
// credentials: 'include', // Include cookies for CORS
|
|
1900
|
+
mode: "cors"
|
|
1901
|
+
// Explicitly set CORS mode
|
|
1902
|
+
});
|
|
1903
|
+
console.log("[SSEClient] Response status:", response.status);
|
|
1904
|
+
console.log("[SSEClient] Response headers:", Object.fromEntries(response.headers.entries()));
|
|
1905
|
+
if (!response.ok) {
|
|
1906
|
+
let errorMessage = `Chat request failed with status ${response.status}`;
|
|
1907
|
+
try {
|
|
1908
|
+
const error = await response.json();
|
|
1909
|
+
errorMessage = error.detail || error.message || errorMessage;
|
|
1910
|
+
} catch (e) {
|
|
1911
|
+
try {
|
|
1912
|
+
const text = await response.text();
|
|
1913
|
+
if (text) errorMessage = text;
|
|
1914
|
+
} catch (textError) {
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
console.error("[SSEClient] Error response:", errorMessage);
|
|
1918
|
+
throw new Error(errorMessage);
|
|
1919
|
+
}
|
|
1920
|
+
const contentType = response.headers.get("content-type");
|
|
1921
|
+
if (!contentType?.includes("text/event-stream")) {
|
|
1922
|
+
console.warn("[SSEClient] Unexpected content-type:", contentType);
|
|
1923
|
+
}
|
|
1924
|
+
try {
|
|
1925
|
+
await this.handleSSEStream(response, callbacks);
|
|
1926
|
+
} finally {
|
|
1927
|
+
this.controllers.delete(connectionId);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
async handleSSEStream(response, callbacks) {
|
|
1931
|
+
if (!response.body) {
|
|
1932
|
+
console.error("[SSEClient] Response body is null");
|
|
1933
|
+
throw new Error("No response body available for streaming");
|
|
1934
|
+
}
|
|
1935
|
+
const reader = response.body.getReader();
|
|
1936
|
+
const decoder = new TextDecoder();
|
|
1937
|
+
let buffer = "";
|
|
1938
|
+
try {
|
|
1939
|
+
console.log("[SSEClient] Starting to read stream...");
|
|
1940
|
+
while (true) {
|
|
1941
|
+
const { done, value } = await reader.read();
|
|
1942
|
+
if (done) {
|
|
1943
|
+
console.log("[SSEClient] Stream ended");
|
|
1944
|
+
break;
|
|
1945
|
+
}
|
|
1946
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1947
|
+
const lines = buffer.split("\n");
|
|
1948
|
+
buffer = lines.pop() || "";
|
|
1949
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1950
|
+
const line = lines[i].trim();
|
|
1951
|
+
if (!line) continue;
|
|
1952
|
+
console.log("[SSEClient] Processing line:", line);
|
|
1953
|
+
if (line.startsWith("event:")) {
|
|
1954
|
+
const event = line.slice(6).trim();
|
|
1955
|
+
console.log("[SSEClient] Event type:", event);
|
|
1956
|
+
const nextLine = lines[i + 1];
|
|
1957
|
+
if (nextLine?.startsWith("data:")) {
|
|
1958
|
+
const dataStr = nextLine.slice(5).trim();
|
|
1959
|
+
console.log("[SSEClient] Event data:", dataStr);
|
|
1960
|
+
try {
|
|
1961
|
+
const data = JSON.parse(dataStr);
|
|
1962
|
+
switch (event) {
|
|
1963
|
+
case "thread":
|
|
1964
|
+
callbacks.onThreadCreated?.(data.thread_id);
|
|
1965
|
+
break;
|
|
1966
|
+
case "message":
|
|
1967
|
+
callbacks.onMessage?.(data.text);
|
|
1968
|
+
break;
|
|
1969
|
+
case "reasoning":
|
|
1970
|
+
callbacks.onReasoning?.(data.text);
|
|
1971
|
+
break;
|
|
1972
|
+
case "complete":
|
|
1973
|
+
callbacks.onComplete?.(data.message_id);
|
|
1974
|
+
break;
|
|
1975
|
+
case "error":
|
|
1976
|
+
callbacks.onError?.(data.error);
|
|
1977
|
+
break;
|
|
1978
|
+
}
|
|
1979
|
+
} catch (e) {
|
|
1980
|
+
console.error("[SSEClient] Failed to parse data:", dataStr, e);
|
|
1981
|
+
}
|
|
1982
|
+
i++;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
} finally {
|
|
1988
|
+
reader.releaseLock();
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
abort(threadId) {
|
|
1992
|
+
if (threadId) {
|
|
1993
|
+
for (const [id3, controller] of this.controllers.entries()) {
|
|
1994
|
+
if (id3.startsWith(threadId)) {
|
|
1995
|
+
controller.abort();
|
|
1996
|
+
this.controllers.delete(id3);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
} else {
|
|
2000
|
+
for (const [id3, controller] of this.controllers.entries()) {
|
|
2001
|
+
controller.abort();
|
|
2002
|
+
}
|
|
2003
|
+
this.controllers.clear();
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
|
|
2008
|
+
// src/lib/services/chatService.ts
|
|
2009
|
+
async function getUserThreads(userId, limit = 20) {
|
|
2010
|
+
const supabase = _getSupabaseInstance();
|
|
2011
|
+
const { data, error } = await supabase.schema("ai").from("chat_threads").select("*").eq("user_id", userId).order("updated_at", { ascending: false }).limit(limit);
|
|
2012
|
+
if (error) throw error;
|
|
2013
|
+
return data;
|
|
2014
|
+
}
|
|
2015
|
+
async function getUserThreadsPaginated(userId, page = 1, pageSize = 20) {
|
|
2016
|
+
const supabase = _getSupabaseInstance();
|
|
2017
|
+
const from = (page - 1) * pageSize;
|
|
2018
|
+
const to = from + pageSize - 1;
|
|
2019
|
+
const { data, error, count } = await supabase.schema("ai").from("chat_threads").select("*", { count: "exact" }).eq("user_id", userId).order("updated_at", { ascending: false }).range(from, to);
|
|
2020
|
+
if (error) throw error;
|
|
2021
|
+
return {
|
|
2022
|
+
threads: data,
|
|
2023
|
+
totalCount: count || 0,
|
|
2024
|
+
totalPages: Math.ceil((count || 0) / pageSize),
|
|
2025
|
+
currentPage: page
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
async function getThreadMessages(threadId, limit = 50, beforePosition) {
|
|
2029
|
+
const supabase = _getSupabaseInstance();
|
|
2030
|
+
let query = supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
|
|
2031
|
+
if (beforePosition !== void 0) {
|
|
2032
|
+
query = query.lt("position", beforePosition);
|
|
2033
|
+
}
|
|
2034
|
+
const { data, error } = await query.limit(limit);
|
|
2035
|
+
if (error) throw error;
|
|
2036
|
+
return data;
|
|
2037
|
+
}
|
|
2038
|
+
async function getAllThreadMessages(threadId) {
|
|
2039
|
+
const supabase = _getSupabaseInstance();
|
|
2040
|
+
const { data, error } = await supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
|
|
2041
|
+
if (error) throw error;
|
|
2042
|
+
return data;
|
|
2043
|
+
}
|
|
2044
|
+
async function updateThreadTitle(threadId, newTitle) {
|
|
2045
|
+
const supabase = _getSupabaseInstance();
|
|
2046
|
+
const { data, error } = await supabase.schema("ai").from("chat_threads").update({
|
|
2047
|
+
title: newTitle,
|
|
2048
|
+
auto_title: false,
|
|
2049
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2050
|
+
}).eq("id", threadId).select().single();
|
|
2051
|
+
if (error) throw error;
|
|
2052
|
+
return data;
|
|
2053
|
+
}
|
|
2054
|
+
async function deleteThread(threadId) {
|
|
2055
|
+
const supabase = _getSupabaseInstance();
|
|
2056
|
+
const { error } = await supabase.schema("ai").from("chat_threads").delete().eq("id", threadId);
|
|
2057
|
+
if (error) throw error;
|
|
2058
|
+
}
|
|
2059
|
+
var AuthContext = createContext({
|
|
2060
|
+
session: null,
|
|
2061
|
+
user: null,
|
|
2062
|
+
loading: true,
|
|
2063
|
+
error: null,
|
|
2064
|
+
signOut: async () => {
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
var useAuth = () => useContext(AuthContext);
|
|
2068
|
+
var AuthProvider = ({ children }) => {
|
|
2069
|
+
const supabase = useSupabase();
|
|
2070
|
+
const { authConfig } = useDashboardConfig();
|
|
2071
|
+
const [session, setSession] = useState(null);
|
|
2072
|
+
const [user, setUser] = useState(null);
|
|
2073
|
+
const [loading, setLoading] = useState(true);
|
|
2074
|
+
const [error, setError] = useState(null);
|
|
2075
|
+
const router = useRouter();
|
|
2076
|
+
const userProfileTable = authConfig?.userProfileTable;
|
|
2077
|
+
const roleColumn = authConfig?.roleColumn || "role";
|
|
2078
|
+
const fetchUserDetails = useCallback(async (supabaseUser) => {
|
|
2079
|
+
if (!supabaseUser) return null;
|
|
2080
|
+
const basicUser = {
|
|
2081
|
+
id: supabaseUser.id,
|
|
2082
|
+
email: supabaseUser.email
|
|
2083
|
+
};
|
|
2084
|
+
if (!userProfileTable || !supabase) return basicUser;
|
|
2085
|
+
try {
|
|
2086
|
+
const timeoutPromise = new Promise(
|
|
2087
|
+
(_, reject) => setTimeout(() => reject(new Error("Profile fetch timeout")), 5e3)
|
|
2088
|
+
);
|
|
2089
|
+
const fetchPromise = supabase.from(userProfileTable).select(roleColumn).eq("id", supabaseUser.id).single();
|
|
2090
|
+
const { data: profile, error: profileError } = await Promise.race([
|
|
2091
|
+
fetchPromise,
|
|
2092
|
+
timeoutPromise
|
|
2093
|
+
]);
|
|
2094
|
+
if (profileError) {
|
|
2095
|
+
if (profileError.message.includes("does not exist") || profileError.message.includes("No rows found") || profileError.code === "PGRST116") {
|
|
2096
|
+
console.log("User profile table not found or user not in table, using basic auth info");
|
|
2097
|
+
return basicUser;
|
|
2098
|
+
}
|
|
2099
|
+
console.error("Error fetching user profile:", profileError);
|
|
2100
|
+
return basicUser;
|
|
2101
|
+
}
|
|
2102
|
+
const roleValue = profile ? profile[roleColumn] : void 0;
|
|
2103
|
+
return { ...basicUser, role: roleValue };
|
|
2104
|
+
} catch (err) {
|
|
2105
|
+
console.error("Error fetching user profile:", err);
|
|
2106
|
+
return basicUser;
|
|
2107
|
+
}
|
|
2108
|
+
}, [supabase, userProfileTable, roleColumn]);
|
|
2109
|
+
useEffect(() => {
|
|
2110
|
+
if (!supabase) return;
|
|
2111
|
+
let mounted = true;
|
|
2112
|
+
const safetyTimeout = setTimeout(() => {
|
|
2113
|
+
if (mounted) {
|
|
2114
|
+
console.warn("Auth initialization taking too long, forcing loading to false");
|
|
2115
|
+
setLoading(false);
|
|
2116
|
+
}
|
|
2117
|
+
}, 1e4);
|
|
2118
|
+
const initializeAuth = async () => {
|
|
2119
|
+
try {
|
|
2120
|
+
const { data: { session: initialSession }, error: sessionError } = await supabase.auth.getSession();
|
|
2121
|
+
if (!mounted) return;
|
|
2122
|
+
if (sessionError) {
|
|
2123
|
+
setError(sessionError);
|
|
2124
|
+
setLoading(false);
|
|
2125
|
+
clearTimeout(safetyTimeout);
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
setSession(initialSession);
|
|
2129
|
+
setLoading(false);
|
|
2130
|
+
if (initialSession?.user) {
|
|
2131
|
+
try {
|
|
2132
|
+
const userDetails = await fetchUserDetails(initialSession.user);
|
|
2133
|
+
if (mounted) {
|
|
2134
|
+
setUser(userDetails);
|
|
2135
|
+
if (userDetails) {
|
|
2136
|
+
identifyCoreUser(userDetails.id, {
|
|
2137
|
+
email: userDetails.email,
|
|
2138
|
+
name: userDetails.email,
|
|
2139
|
+
// using email as the display name for now
|
|
2140
|
+
role: userDetails.role
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
} catch (err) {
|
|
2145
|
+
console.error("Error fetching user details during init:", err);
|
|
2146
|
+
if (mounted) {
|
|
2147
|
+
setUser({
|
|
2148
|
+
id: initialSession.user.id,
|
|
2149
|
+
email: initialSession.user.email
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
} catch (err) {
|
|
2155
|
+
if (mounted) setError(err instanceof Error ? err : new Error("Failed to initialize auth"));
|
|
2156
|
+
} finally {
|
|
2157
|
+
if (mounted) {
|
|
2158
|
+
setLoading(false);
|
|
2159
|
+
clearTimeout(safetyTimeout);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
initializeAuth();
|
|
2164
|
+
const { data: { subscription } } = supabase.auth.onAuthStateChange(async (_event, currentSession) => {
|
|
2165
|
+
if (!mounted) return;
|
|
2166
|
+
setSession(currentSession);
|
|
2167
|
+
setUser(null);
|
|
2168
|
+
setLoading(false);
|
|
2169
|
+
if (currentSession?.user) {
|
|
2170
|
+
try {
|
|
2171
|
+
const userDetails = await fetchUserDetails(currentSession.user);
|
|
2172
|
+
if (mounted) {
|
|
2173
|
+
setUser(userDetails);
|
|
2174
|
+
if (userDetails) {
|
|
2175
|
+
identifyCoreUser(userDetails.id, {
|
|
2176
|
+
email: userDetails.email,
|
|
2177
|
+
name: userDetails.email,
|
|
2178
|
+
role: userDetails.role
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
} catch (err) {
|
|
2183
|
+
console.error("Error fetching user details on auth state change:", err);
|
|
2184
|
+
if (mounted) {
|
|
2185
|
+
setUser({
|
|
2186
|
+
id: currentSession.user.id,
|
|
2187
|
+
email: currentSession.user.email
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
if (mounted) setLoading(false);
|
|
2193
|
+
});
|
|
2194
|
+
return () => {
|
|
2195
|
+
mounted = false;
|
|
2196
|
+
clearTimeout(safetyTimeout);
|
|
2197
|
+
subscription?.unsubscribe();
|
|
2198
|
+
};
|
|
2199
|
+
}, [supabase, fetchUserDetails]);
|
|
2200
|
+
const signOut = async () => {
|
|
2201
|
+
if (!supabase) return;
|
|
2202
|
+
setLoading(true);
|
|
2203
|
+
const { error: signOutError } = await supabase.auth.signOut();
|
|
2204
|
+
if (signOutError) setError(signOutError);
|
|
2205
|
+
const logoutRedirectPath = authConfig?.defaultLogoutRedirect || "/login";
|
|
2206
|
+
if (router && router.pathname !== logoutRedirectPath && !router.pathname.startsWith(logoutRedirectPath)) {
|
|
2207
|
+
router.replace(logoutRedirectPath);
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
return /* @__PURE__ */ jsx(AuthContext.Provider, { value: { session, user, loading, error, signOut }, children });
|
|
2211
|
+
};
|
|
2212
|
+
var defaultContextValue = {
|
|
2213
|
+
components: {},
|
|
2214
|
+
hooks: {},
|
|
2215
|
+
pages: {}
|
|
2216
|
+
};
|
|
2217
|
+
var DashboardOverridesContext = createContext(defaultContextValue);
|
|
2218
|
+
var DashboardOverridesProvider = ({
|
|
2219
|
+
overrides = defaultContextValue,
|
|
2220
|
+
children
|
|
2221
|
+
}) => {
|
|
2222
|
+
const normalizedOverrides = useMemo(() => {
|
|
2223
|
+
return {
|
|
2224
|
+
components: overrides.components || {},
|
|
2225
|
+
hooks: overrides.hooks || {},
|
|
2226
|
+
pages: overrides.pages || {}
|
|
2227
|
+
};
|
|
2228
|
+
}, [overrides]);
|
|
2229
|
+
return /* @__PURE__ */ jsx(DashboardOverridesContext.Provider, { value: normalizedOverrides, children });
|
|
2230
|
+
};
|
|
2231
|
+
function useComponentOverride(key, Default) {
|
|
2232
|
+
const { components = {} } = useContext(DashboardOverridesContext);
|
|
2233
|
+
return components[key] ?? Default;
|
|
2234
|
+
}
|
|
2235
|
+
function useHookOverride(key, Default) {
|
|
2236
|
+
const { hooks = {} } = useContext(DashboardOverridesContext);
|
|
2237
|
+
return hooks[key] ?? Default;
|
|
2238
|
+
}
|
|
2239
|
+
function usePageOverride(key, Default) {
|
|
2240
|
+
const { pages = {} } = useContext(DashboardOverridesContext);
|
|
2241
|
+
return pages[key] ?? Default;
|
|
2242
|
+
}
|
|
2243
|
+
function useOverrides() {
|
|
2244
|
+
return useContext(DashboardOverridesContext);
|
|
2245
|
+
}
|
|
2246
|
+
var SupabaseContext = createContext(void 0);
|
|
2247
|
+
var SupabaseProvider = ({ client, children }) => {
|
|
2248
|
+
_setSupabaseInstance(client);
|
|
2249
|
+
useEffect(() => {
|
|
2250
|
+
_setSupabaseInstance(client);
|
|
2251
|
+
}, [client]);
|
|
2252
|
+
const contextValue = useMemo(() => ({ supabase: client }), [client]);
|
|
2253
|
+
return /* @__PURE__ */ jsx(SupabaseContext.Provider, { value: contextValue, children });
|
|
2254
|
+
};
|
|
2255
|
+
var useSupabase = () => {
|
|
2256
|
+
const context = useContext(SupabaseContext);
|
|
2257
|
+
if (context === void 0) {
|
|
2258
|
+
throw new Error("useSupabase must be used within a SupabaseProvider.");
|
|
2259
|
+
}
|
|
2260
|
+
return context.supabase;
|
|
2261
|
+
};
|
|
2262
|
+
var DEFAULT_COMPANY_ID = "default-company-id";
|
|
2263
|
+
var useWorkspaceMetrics = (workspaceId) => {
|
|
2264
|
+
const supabase = useSupabase();
|
|
2265
|
+
const entityConfig = useEntityConfig();
|
|
2266
|
+
useDatabaseConfig();
|
|
2267
|
+
const dateTimeConfig = useDateTimeConfig();
|
|
2268
|
+
const [workspaceMetrics, setWorkspaceMetrics] = useState(null);
|
|
2269
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
2270
|
+
const [error, setError] = useState(null);
|
|
2271
|
+
const fetchWorkspaceMetrics = useCallback(async () => {
|
|
2272
|
+
try {
|
|
2273
|
+
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
2274
|
+
const { data, error: fetchError } = await supabase.from("overview_workspace_metrics").select("*").eq("workspace_id", workspaceId).eq("date", operationalDate).single();
|
|
2275
|
+
if (fetchError) throw fetchError;
|
|
2276
|
+
setWorkspaceMetrics(data);
|
|
2277
|
+
} catch (err) {
|
|
2278
|
+
setError({ message: err.message, code: err.code });
|
|
2279
|
+
console.error("Error fetching workspace metrics:", err);
|
|
2280
|
+
} finally {
|
|
2281
|
+
setIsLoading(false);
|
|
2282
|
+
}
|
|
2283
|
+
}, [supabase, workspaceId, dateTimeConfig.defaultTimezone]);
|
|
2284
|
+
useEffect(() => {
|
|
2285
|
+
let channels = [];
|
|
2286
|
+
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
2287
|
+
const setupSubscriptions = () => {
|
|
2288
|
+
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
2289
|
+
const metricsTablePrefix = getMetricsTablePrefix();
|
|
2290
|
+
const metricsTable = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2291
|
+
const metricsChannel = supabase.channel("workspace-metrics").on(
|
|
2292
|
+
"postgres_changes",
|
|
2293
|
+
{
|
|
2294
|
+
event: "*",
|
|
2295
|
+
schema: "public",
|
|
2296
|
+
table: metricsTable,
|
|
2297
|
+
filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
|
|
2298
|
+
},
|
|
2299
|
+
async (payload) => {
|
|
2300
|
+
console.log(`Received ${metricsTablePrefix} update:`, payload);
|
|
2301
|
+
await fetchWorkspaceMetrics();
|
|
2302
|
+
}
|
|
2303
|
+
).subscribe((status) => {
|
|
2304
|
+
console.log(`${metricsTablePrefix} subscription status:`, status);
|
|
2305
|
+
});
|
|
2306
|
+
const overviewChannel = supabase.channel("workspace-overview-metrics").on(
|
|
2307
|
+
"postgres_changes",
|
|
2308
|
+
{
|
|
2309
|
+
event: "*",
|
|
2310
|
+
schema: "public",
|
|
2311
|
+
table: "overview_workspace_metrics",
|
|
2312
|
+
filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
|
|
2313
|
+
},
|
|
2314
|
+
async (payload) => {
|
|
2315
|
+
console.log("Received overview metrics update:", payload);
|
|
2316
|
+
await fetchWorkspaceMetrics();
|
|
2317
|
+
}
|
|
2318
|
+
).subscribe((status) => {
|
|
2319
|
+
console.log("Overview metrics subscription status:", status);
|
|
2320
|
+
});
|
|
2321
|
+
channels = [metricsChannel, overviewChannel];
|
|
2322
|
+
};
|
|
2323
|
+
fetchWorkspaceMetrics();
|
|
2324
|
+
setupSubscriptions();
|
|
2325
|
+
return () => {
|
|
2326
|
+
channels.forEach((channel) => {
|
|
2327
|
+
console.log("Cleaning up channel subscription");
|
|
2328
|
+
supabase.removeChannel(channel);
|
|
2329
|
+
});
|
|
2330
|
+
};
|
|
2331
|
+
}, [supabase, workspaceId, fetchWorkspaceMetrics, entityConfig.companyId, dateTimeConfig.defaultTimezone]);
|
|
2332
|
+
return { workspaceMetrics, isLoading, error, refetch: fetchWorkspaceMetrics };
|
|
2333
|
+
};
|
|
2334
|
+
var useLineMetrics = (lineId) => {
|
|
2335
|
+
const supabase = useSupabase();
|
|
2336
|
+
const dateTimeConfig = useDateTimeConfig();
|
|
2337
|
+
const [lineMetrics, setLineMetrics] = useState(null);
|
|
2338
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
2339
|
+
const [error, setError] = useState(null);
|
|
2340
|
+
const fetchLineMetrics = useCallback(async () => {
|
|
2341
|
+
try {
|
|
2342
|
+
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
2343
|
+
const { data, error: fetchError } = await supabase.from("overview_line_metrics").select("*").eq("line_id", lineId).eq("date", operationalDate).single();
|
|
2344
|
+
if (fetchError) throw fetchError;
|
|
2345
|
+
setLineMetrics(data);
|
|
2346
|
+
} catch (err) {
|
|
2347
|
+
setError({ message: err.message, code: err.code });
|
|
2348
|
+
console.error("Error fetching line metrics:", err);
|
|
2349
|
+
} finally {
|
|
2350
|
+
setIsLoading(false);
|
|
2351
|
+
}
|
|
2352
|
+
}, [supabase, lineId, dateTimeConfig.defaultTimezone]);
|
|
2353
|
+
useEffect(() => {
|
|
2354
|
+
let channels = [];
|
|
2355
|
+
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
2356
|
+
const setupSubscriptions = () => {
|
|
2357
|
+
const lineMetricsChannel = supabase.channel("line-base-metrics").on(
|
|
2358
|
+
"postgres_changes",
|
|
2359
|
+
{
|
|
2360
|
+
event: "*",
|
|
2361
|
+
schema: "public",
|
|
2362
|
+
table: "line_metrics",
|
|
2363
|
+
filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
|
|
2364
|
+
},
|
|
2365
|
+
async (payload) => {
|
|
2366
|
+
console.log("Received line metrics update:", payload);
|
|
2367
|
+
await fetchLineMetrics();
|
|
2368
|
+
}
|
|
2369
|
+
).subscribe((status) => {
|
|
2370
|
+
console.log("Line metrics subscription status:", status);
|
|
2371
|
+
});
|
|
2372
|
+
const overviewChannel = supabase.channel("line-overview-metrics").on(
|
|
2373
|
+
"postgres_changes",
|
|
2374
|
+
{
|
|
2375
|
+
event: "*",
|
|
2376
|
+
schema: "public",
|
|
2377
|
+
table: "overview_line_metrics",
|
|
2378
|
+
filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
|
|
2379
|
+
},
|
|
2380
|
+
async (payload) => {
|
|
2381
|
+
console.log("Received line overview update:", payload);
|
|
2382
|
+
await fetchLineMetrics();
|
|
2383
|
+
}
|
|
2384
|
+
).subscribe((status) => {
|
|
2385
|
+
console.log("Line overview subscription status:", status);
|
|
2386
|
+
});
|
|
2387
|
+
channels = [lineMetricsChannel, overviewChannel];
|
|
2388
|
+
};
|
|
2389
|
+
fetchLineMetrics();
|
|
2390
|
+
setupSubscriptions();
|
|
2391
|
+
return () => {
|
|
2392
|
+
channels.forEach((channel) => {
|
|
2393
|
+
console.log("Cleaning up channel subscription");
|
|
2394
|
+
supabase.removeChannel(channel);
|
|
2395
|
+
});
|
|
2396
|
+
};
|
|
2397
|
+
}, [supabase, lineId, fetchLineMetrics, dateTimeConfig.defaultTimezone]);
|
|
2398
|
+
return { lineMetrics, isLoading, error, refetch: fetchLineMetrics };
|
|
2399
|
+
};
|
|
2400
|
+
var useMetrics = (tableName, options) => {
|
|
2401
|
+
const supabase = useSupabase();
|
|
2402
|
+
const entityConfig = useEntityConfig();
|
|
2403
|
+
const [data, setData] = useState([]);
|
|
2404
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
2405
|
+
const [error, setError] = useState(null);
|
|
2406
|
+
const channelRef = useRef(null);
|
|
2407
|
+
useEffect(() => {
|
|
2408
|
+
const fetchData = async () => {
|
|
2409
|
+
try {
|
|
2410
|
+
setIsLoading(true);
|
|
2411
|
+
setError(null);
|
|
2412
|
+
let actualTableName = tableName;
|
|
2413
|
+
if (tableName === "metrics") {
|
|
2414
|
+
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
2415
|
+
const metricsTablePrefix = getMetricsTablePrefix(companyId);
|
|
2416
|
+
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2417
|
+
}
|
|
2418
|
+
let query = supabase.from(actualTableName).select("*");
|
|
2419
|
+
if (options?.filter) {
|
|
2420
|
+
Object.entries(options.filter).forEach(([key, value]) => {
|
|
2421
|
+
query = query.eq(key, value);
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
const { data: result, error: fetchError } = await query;
|
|
2425
|
+
if (fetchError) throw fetchError;
|
|
2426
|
+
setData(result);
|
|
2427
|
+
} catch (err) {
|
|
2428
|
+
console.error(`Error fetching data from ${tableName}:`, err);
|
|
2429
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
2430
|
+
} finally {
|
|
2431
|
+
setIsLoading(false);
|
|
2432
|
+
}
|
|
2433
|
+
};
|
|
2434
|
+
const setupSubscription = () => {
|
|
2435
|
+
if (!options?.realtime) return;
|
|
2436
|
+
let actualTableName = tableName;
|
|
2437
|
+
if (tableName === "metrics") {
|
|
2438
|
+
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
2439
|
+
const metricsTablePrefix = getMetricsTablePrefix();
|
|
2440
|
+
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2441
|
+
}
|
|
2442
|
+
const filter2 = {};
|
|
2443
|
+
if (options?.filter) {
|
|
2444
|
+
Object.entries(options.filter).forEach(([key, value]) => {
|
|
2445
|
+
filter2[`${key}=eq.${value}`] = value;
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
channelRef.current = supabase.channel(`${tableName}-changes`).on(
|
|
2449
|
+
"postgres_changes",
|
|
2450
|
+
{
|
|
2451
|
+
event: "*",
|
|
2452
|
+
schema: "public",
|
|
2453
|
+
table: actualTableName,
|
|
2454
|
+
filter: Object.keys(filter2).length > 0 ? Object.keys(filter2).join(",") : void 0
|
|
2455
|
+
},
|
|
2456
|
+
() => {
|
|
2457
|
+
fetchData();
|
|
2458
|
+
}
|
|
2459
|
+
).subscribe();
|
|
2460
|
+
};
|
|
2461
|
+
fetchData();
|
|
2462
|
+
setupSubscription();
|
|
2463
|
+
return () => {
|
|
2464
|
+
if (channelRef.current) {
|
|
2465
|
+
supabase.removeChannel(channelRef.current);
|
|
2466
|
+
}
|
|
2467
|
+
};
|
|
2468
|
+
}, [supabase, tableName, options, entityConfig.companyId]);
|
|
2469
|
+
const refetch = async () => {
|
|
2470
|
+
setIsLoading(true);
|
|
2471
|
+
try {
|
|
2472
|
+
let actualTableName = tableName;
|
|
2473
|
+
if (tableName === "metrics") {
|
|
2474
|
+
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
2475
|
+
const metricsTablePrefix = getMetricsTablePrefix(companyId);
|
|
2476
|
+
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2477
|
+
}
|
|
2478
|
+
let query = supabase.from(actualTableName).select("*");
|
|
2479
|
+
if (options?.filter) {
|
|
2480
|
+
Object.entries(options.filter).forEach(([key, value]) => {
|
|
2481
|
+
query = query.eq(key, value);
|
|
2482
|
+
});
|
|
2483
|
+
}
|
|
2484
|
+
const { data: result, error: fetchError } = await query;
|
|
2485
|
+
if (fetchError) throw fetchError;
|
|
2486
|
+
setData(result);
|
|
2487
|
+
setError(null);
|
|
2488
|
+
} catch (err) {
|
|
2489
|
+
console.error(`Error refetching data from ${tableName}:`, err);
|
|
2490
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
2491
|
+
} finally {
|
|
2492
|
+
setIsLoading(false);
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
return { data, isLoading, error, refetch };
|
|
2496
|
+
};
|
|
2497
|
+
var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId) => {
|
|
2498
|
+
const entityConfig = useEntityConfig();
|
|
2499
|
+
const databaseConfig = useDatabaseConfig();
|
|
2500
|
+
const dateTimeConfig = useDateTimeConfig();
|
|
2501
|
+
const shiftConfig = useShiftConfig();
|
|
2502
|
+
const workspaceConfig = useWorkspaceConfig();
|
|
2503
|
+
const supabase = useSupabase();
|
|
2504
|
+
const [metrics2, setMetrics] = useState(null);
|
|
2505
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
2506
|
+
const [error, setError] = useState(null);
|
|
2507
|
+
const updateQueueRef = useRef(false);
|
|
2508
|
+
const isFetchingRef = useRef(false);
|
|
2509
|
+
const timeoutRef = useRef(null);
|
|
2510
|
+
const channelRef = useRef(null);
|
|
2511
|
+
const schema = databaseConfig.schema ?? "public";
|
|
2512
|
+
const companyId = entityConfig.companyId || "";
|
|
2513
|
+
const metricsTablePrefix = getMetricsTablePrefix();
|
|
2514
|
+
const metricsTable = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2515
|
+
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
2516
|
+
const workspaceMetricsBaseTable = databaseConfig.tables?.workspaces ?? "workspace_metrics";
|
|
2517
|
+
const workspaceActionsTable = databaseConfig.tables?.actions ?? "workspace_actions";
|
|
2518
|
+
const fetchMetrics = useCallback(async () => {
|
|
2519
|
+
if (!workspaceId || isFetchingRef.current) return;
|
|
2520
|
+
try {
|
|
2521
|
+
isFetchingRef.current = true;
|
|
2522
|
+
const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
|
|
2523
|
+
const queryDate = date || currentShift.date;
|
|
2524
|
+
const queryShiftId = shiftId !== void 0 ? shiftId : currentShift.shiftId;
|
|
2525
|
+
console.log("[useWorkspaceDetailedMetrics] Using shift ID:", queryShiftId, "from input shift:", shiftId);
|
|
2526
|
+
console.log("[useWorkspaceDetailedMetrics] Using date:", queryDate, "from input date:", date);
|
|
2527
|
+
console.log(`[useWorkspaceDetailedMetrics] Querying ${metricsTable} for workspace: ${workspaceId}, date: ${queryDate}, shift: ${queryShiftId}`);
|
|
2528
|
+
const { data, error: fetchError } = await supabase.from(metricsTable).select("*").eq("workspace_id", workspaceId).eq("date", queryDate).eq("shift_id", queryShiftId).maybeSingle();
|
|
2529
|
+
if (fetchError) throw fetchError;
|
|
2530
|
+
if (!data && !date && shiftId === void 0) {
|
|
2531
|
+
console.log("[useWorkspaceDetailedMetrics] No data found for current date/shift, attempting to find most recent data...");
|
|
2532
|
+
const { data: recentData, error: recentError } = await supabase.from(metricsTable).select("*").eq("workspace_id", workspaceId).order("date", { ascending: false }).order("shift_id", { ascending: false }).limit(1).maybeSingle();
|
|
2533
|
+
if (recentError) throw recentError;
|
|
2534
|
+
if (recentData) {
|
|
2535
|
+
console.log(`[useWorkspaceDetailedMetrics] Found fallback data from date: ${recentData.date}, shift: ${recentData.shift_id}`);
|
|
2536
|
+
const outputDifference2 = (recentData.total_output || 0) - (recentData.ideal_output || 0);
|
|
2537
|
+
const workspaceMatch2 = recentData.workspace_name?.match(/WS(\d+)/);
|
|
2538
|
+
const workspaceNumber2 = workspaceMatch2 ? parseInt(workspaceMatch2[1]) : 0;
|
|
2539
|
+
const specialWsStart2 = workspaceConfig.specialWorkspaces?.startId ?? 19;
|
|
2540
|
+
const specialWsEnd2 = workspaceConfig.specialWorkspaces?.endId ?? 34;
|
|
2541
|
+
const isSpecialWorkspace2 = workspaceNumber2 >= specialWsStart2 && workspaceNumber2 <= specialWsEnd2;
|
|
2542
|
+
const outputHourly2 = recentData.output_hourly || {};
|
|
2543
|
+
const hasOutputHourlyData2 = outputHourly2 && typeof outputHourly2 === "object" && Object.keys(outputHourly2).length > 0;
|
|
2544
|
+
let hourlyActionCounts2 = [];
|
|
2545
|
+
if (hasOutputHourlyData2) {
|
|
2546
|
+
console.log("Using new output_hourly column for workspace (fallback):", recentData.workspace_name);
|
|
2547
|
+
console.log("Raw output_hourly data (fallback):", outputHourly2);
|
|
2548
|
+
const isNightShift = recentData.shift_id === 1;
|
|
2549
|
+
const shiftStart = recentData.shift_start || (isNightShift ? "22:00" : "06:00");
|
|
2550
|
+
const shiftEnd = recentData.shift_end || (isNightShift ? "06:00" : "14:00");
|
|
2551
|
+
const startHour = parseInt(shiftStart.split(":")[0]);
|
|
2552
|
+
let expectedHours = [];
|
|
2553
|
+
if (isNightShift) {
|
|
2554
|
+
for (let i = 0; i < 9; i++) {
|
|
2555
|
+
expectedHours.push((startHour + i) % 24);
|
|
2556
|
+
}
|
|
2557
|
+
} else {
|
|
2558
|
+
for (let i = 0; i < 9; i++) {
|
|
2559
|
+
expectedHours.push((startHour + i) % 24);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
console.log("Expected shift hours (fallback):", expectedHours);
|
|
2563
|
+
console.log("Available data hours (fallback):", Object.keys(outputHourly2));
|
|
2564
|
+
hourlyActionCounts2 = expectedHours.map((expectedHour) => {
|
|
2565
|
+
let hourData = outputHourly2[expectedHour.toString()];
|
|
2566
|
+
if (!hourData && isNightShift) {
|
|
2567
|
+
for (const [storedHour, data2] of Object.entries(outputHourly2)) {
|
|
2568
|
+
if (Array.isArray(data2) && data2.length > 0 && data2.some((val) => val > 0)) {
|
|
2569
|
+
if (storedHour === "18" && expectedHour === 1) {
|
|
2570
|
+
hourData = data2;
|
|
2571
|
+
console.log(`Mapping stored hour ${storedHour} to expected hour ${expectedHour} (fallback)`);
|
|
2572
|
+
break;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
return Array.isArray(hourData) ? hourData.reduce((sum, count) => sum + (count || 0), 0) : 0;
|
|
2578
|
+
});
|
|
2579
|
+
console.log("Final hourly action counts (fallback):", hourlyActionCounts2);
|
|
2580
|
+
} else {
|
|
2581
|
+
console.log("Using output_array fallback for workspace (fallback):", recentData.workspace_name);
|
|
2582
|
+
const minuteByMinuteArray = recentData.output_array || [];
|
|
2583
|
+
if (isSpecialWorkspace2) {
|
|
2584
|
+
const last40Readings = minuteByMinuteArray.slice(Math.max(0, minuteByMinuteArray.length - 40));
|
|
2585
|
+
hourlyActionCounts2 = last40Readings;
|
|
2586
|
+
} else {
|
|
2587
|
+
for (let i = 0; i < minuteByMinuteArray.length; i += 60) {
|
|
2588
|
+
const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
|
|
2589
|
+
const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
|
|
2590
|
+
hourlyActionCounts2.push(hourlySum);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
const transformedData2 = {
|
|
2595
|
+
workspace_id: recentData.workspace_id,
|
|
2596
|
+
workspace_name: recentData.workspace_name,
|
|
2597
|
+
line_id: recentData.line_id,
|
|
2598
|
+
line_name: recentData.line_name || "Line 1",
|
|
2599
|
+
company_id: recentData.company_id || companyId,
|
|
2600
|
+
company_name: recentData.company_name || "Nahar Group",
|
|
2601
|
+
date: recentData.date,
|
|
2602
|
+
shift_id: recentData.shift_id,
|
|
2603
|
+
action_name: recentData.action_name || "",
|
|
2604
|
+
shift_start: recentData.shift_start || "06:00",
|
|
2605
|
+
shift_end: recentData.shift_end || "14:00",
|
|
2606
|
+
shift_type: recentData.shift_type || (recentData.shift_id === 0 ? "Day" : "Night"),
|
|
2607
|
+
pph_threshold: recentData.pph_threshold || 0,
|
|
2608
|
+
target_output: recentData.total_day_output || 0,
|
|
2609
|
+
avg_pph: recentData.avg_pph || 0,
|
|
2610
|
+
avg_cycle_time: recentData.avg_cycle_time || 0,
|
|
2611
|
+
ideal_cycle_time: recentData.ideal_cycle_time || 0,
|
|
2612
|
+
avg_efficiency: recentData.efficiency || 0,
|
|
2613
|
+
total_actions: recentData.total_output || 0,
|
|
2614
|
+
hourly_action_counts: hourlyActionCounts2,
|
|
2615
|
+
// Now uses the NEW logic with fallback
|
|
2616
|
+
workspace_rank: recentData.workspace_rank || 0,
|
|
2617
|
+
total_workspaces: recentData.total_workspaces || workspaceConfig.totalWorkspaces || 42,
|
|
2618
|
+
ideal_output_until_now: recentData.ideal_output || 0,
|
|
2619
|
+
output_difference: outputDifference2,
|
|
2620
|
+
idle_time: recentData.idle_time || 0,
|
|
2621
|
+
idle_time_hourly: recentData.idle_time_hourly || void 0,
|
|
2622
|
+
...recentData.compliance_efficiency !== void 0 && { compliance_efficiency: recentData.compliance_efficiency },
|
|
2623
|
+
...recentData.sop_check !== void 0 && { sop_check: recentData.sop_check }
|
|
2624
|
+
};
|
|
2625
|
+
setMetrics(transformedData2);
|
|
2626
|
+
setIsLoading(false);
|
|
2627
|
+
updateQueueRef.current = false;
|
|
2628
|
+
isFetchingRef.current = false;
|
|
2629
|
+
return;
|
|
2630
|
+
} else {
|
|
2631
|
+
console.warn("[useWorkspaceDetailedMetrics] No data found for workspace:", workspaceId, "at all");
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
if (!data) {
|
|
2635
|
+
console.warn("[useWorkspaceDetailedMetrics] No detailed metrics found for workspace:", workspaceId);
|
|
2636
|
+
setMetrics(null);
|
|
2637
|
+
setIsLoading(false);
|
|
2638
|
+
updateQueueRef.current = false;
|
|
2639
|
+
isFetchingRef.current = false;
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
const outputDifference = (data.total_output || 0) - (data.ideal_output || 0);
|
|
2643
|
+
const workspaceMatch = data.workspace_name?.match(/WS(\d+)/);
|
|
2644
|
+
const workspaceNumber = workspaceMatch ? parseInt(workspaceMatch[1]) : 0;
|
|
2645
|
+
const specialWsStart = workspaceConfig.specialWorkspaces?.startId ?? 19;
|
|
2646
|
+
const specialWsEnd = workspaceConfig.specialWorkspaces?.endId ?? 34;
|
|
2647
|
+
const isSpecialWorkspace = workspaceNumber >= specialWsStart && workspaceNumber <= specialWsEnd;
|
|
2648
|
+
const outputHourly = data.output_hourly || {};
|
|
2649
|
+
console.log("[DEBUG] Raw data.output_hourly:", data.output_hourly);
|
|
2650
|
+
console.log("[DEBUG] outputHourly after || {}:", outputHourly);
|
|
2651
|
+
console.log("[DEBUG] typeof outputHourly:", typeof outputHourly);
|
|
2652
|
+
console.log("[DEBUG] Object.keys(outputHourly):", Object.keys(outputHourly));
|
|
2653
|
+
console.log("[DEBUG] Object.keys(outputHourly).length:", Object.keys(outputHourly).length);
|
|
2654
|
+
const hasOutputHourlyData = outputHourly && typeof outputHourly === "object" && Object.keys(outputHourly).length > 0;
|
|
2655
|
+
console.log("[DEBUG] hasOutputHourlyData:", hasOutputHourlyData);
|
|
2656
|
+
let hourlyActionCounts = [];
|
|
2657
|
+
if (hasOutputHourlyData) {
|
|
2658
|
+
console.log("\u2705 Using new output_hourly column for workspace:", data.workspace_name);
|
|
2659
|
+
console.log("Raw output_hourly data:", JSON.stringify(outputHourly));
|
|
2660
|
+
const isNightShift = data.shift_id === 1;
|
|
2661
|
+
const shiftStart = data.shift_start || (isNightShift ? "22:00" : "06:00");
|
|
2662
|
+
const shiftEnd = data.shift_end || (isNightShift ? "06:00" : "14:00");
|
|
2663
|
+
const startHour = parseInt(shiftStart.split(":")[0]);
|
|
2664
|
+
let expectedHours = [];
|
|
2665
|
+
if (isNightShift) {
|
|
2666
|
+
for (let i = 0; i < 9; i++) {
|
|
2667
|
+
expectedHours.push((startHour + i) % 24);
|
|
2668
|
+
}
|
|
2669
|
+
} else {
|
|
2670
|
+
for (let i = 0; i < 9; i++) {
|
|
2671
|
+
expectedHours.push((startHour + i) % 24);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
console.log("Expected shift hours:", expectedHours);
|
|
2675
|
+
console.log("Available data hours:", Object.keys(outputHourly));
|
|
2676
|
+
hourlyActionCounts = expectedHours.map((expectedHour) => {
|
|
2677
|
+
let hourData = outputHourly[expectedHour.toString()];
|
|
2678
|
+
if (!hourData && isNightShift) {
|
|
2679
|
+
for (const [storedHour, data2] of Object.entries(outputHourly)) {
|
|
2680
|
+
if (Array.isArray(data2) && data2.length > 0 && data2.some((val) => val > 0)) {
|
|
2681
|
+
if (storedHour === "18" && expectedHour === 1) {
|
|
2682
|
+
hourData = data2;
|
|
2683
|
+
console.log(`Mapping stored hour ${storedHour} to expected hour ${expectedHour}`);
|
|
2684
|
+
break;
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
return Array.isArray(hourData) ? hourData.reduce((sum, count) => sum + (count || 0), 0) : 0;
|
|
2690
|
+
});
|
|
2691
|
+
console.log("Final hourly action counts:", hourlyActionCounts);
|
|
2692
|
+
} else {
|
|
2693
|
+
console.log("\u274C Using output_array fallback for workspace:", data.workspace_name);
|
|
2694
|
+
console.log("[DEBUG] Fallback reason - hasOutputHourlyData is false");
|
|
2695
|
+
console.log("[DEBUG] data.output_hourly was:", data.output_hourly);
|
|
2696
|
+
const minuteByMinuteArray = data.output_array || [];
|
|
2697
|
+
if (isSpecialWorkspace) {
|
|
2698
|
+
const last40Readings = minuteByMinuteArray.slice(Math.max(0, minuteByMinuteArray.length - 40));
|
|
2699
|
+
hourlyActionCounts = last40Readings;
|
|
2700
|
+
} else {
|
|
2701
|
+
for (let i = 0; i < minuteByMinuteArray.length; i += 60) {
|
|
2702
|
+
const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
|
|
2703
|
+
const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
|
|
2704
|
+
hourlyActionCounts.push(hourlySum);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
console.log("Final hourly action counts:", hourlyActionCounts);
|
|
2708
|
+
}
|
|
2709
|
+
const transformedData = {
|
|
2710
|
+
workspace_id: data.workspace_id,
|
|
2711
|
+
workspace_name: data.workspace_name,
|
|
2712
|
+
line_id: data.line_id,
|
|
2713
|
+
line_name: data.line_name || "Line 1",
|
|
2714
|
+
company_id: data.company_id || companyId,
|
|
2715
|
+
company_name: data.company_name || "Nahar Group",
|
|
2716
|
+
date: data.date,
|
|
2717
|
+
shift_id: data.shift_id,
|
|
2718
|
+
action_name: data.action_name || "",
|
|
2719
|
+
shift_start: data.shift_start || "06:00",
|
|
2720
|
+
shift_end: data.shift_end || "14:00",
|
|
2721
|
+
shift_type: data.shift_type || (data.shift_id === 0 ? "Day" : "Night"),
|
|
2722
|
+
pph_threshold: data.pph_threshold || 0,
|
|
2723
|
+
target_output: data.total_day_output || 0,
|
|
2724
|
+
avg_pph: data.avg_pph || 0,
|
|
2725
|
+
avg_cycle_time: data.avg_cycle_time || 0,
|
|
2726
|
+
ideal_cycle_time: data.ideal_cycle_time || 0,
|
|
2727
|
+
avg_efficiency: data.efficiency || 0,
|
|
2728
|
+
total_actions: data.total_output || 0,
|
|
2729
|
+
hourly_action_counts: hourlyActionCounts,
|
|
2730
|
+
workspace_rank: data.workspace_rank || 0,
|
|
2731
|
+
total_workspaces: data.total_workspaces || workspaceConfig.totalWorkspaces || 42,
|
|
2732
|
+
ideal_output_until_now: data.ideal_output || 0,
|
|
2733
|
+
output_difference: outputDifference,
|
|
2734
|
+
idle_time: data.idle_time || 0,
|
|
2735
|
+
// Add idle_time from performance_metrics table
|
|
2736
|
+
idle_time_hourly: data.idle_time_hourly || void 0,
|
|
2737
|
+
// Add idle_time_hourly from performance_metrics table
|
|
2738
|
+
...data.compliance_efficiency !== void 0 && { compliance_efficiency: data.compliance_efficiency },
|
|
2739
|
+
...data.sop_check !== void 0 && { sop_check: data.sop_check }
|
|
2740
|
+
};
|
|
2741
|
+
setMetrics(transformedData);
|
|
2742
|
+
} catch (err) {
|
|
2743
|
+
console.error("Error fetching workspace metrics:", err);
|
|
2744
|
+
setError({ message: err.message, code: err.code });
|
|
2745
|
+
} finally {
|
|
2746
|
+
isFetchingRef.current = false;
|
|
2747
|
+
updateQueueRef.current = false;
|
|
2748
|
+
setIsLoading(false);
|
|
2749
|
+
}
|
|
2750
|
+
}, [supabase, workspaceId, date, shiftId, metricsTable, defaultTimezone, shiftConfig, workspaceConfig, companyId]);
|
|
2751
|
+
const queueUpdate = useCallback(() => {
|
|
2752
|
+
if (!workspaceId || updateQueueRef.current) return;
|
|
2753
|
+
updateQueueRef.current = true;
|
|
2754
|
+
if (timeoutRef.current) {
|
|
2755
|
+
clearTimeout(timeoutRef.current);
|
|
2756
|
+
}
|
|
2757
|
+
timeoutRef.current = setTimeout(() => {
|
|
2758
|
+
fetchMetrics();
|
|
2759
|
+
}, 500);
|
|
2760
|
+
}, [fetchMetrics, workspaceId]);
|
|
2761
|
+
const setupSubscription = useCallback(() => {
|
|
2762
|
+
if (!workspaceId) return;
|
|
2763
|
+
if (channelRef.current) {
|
|
2764
|
+
supabase.removeChannel(channelRef.current);
|
|
2765
|
+
}
|
|
2766
|
+
channelRef.current = supabase.channel("workspace-detailed-metrics").on(
|
|
2767
|
+
"postgres_changes",
|
|
2768
|
+
{
|
|
2769
|
+
event: "*",
|
|
2770
|
+
schema,
|
|
2771
|
+
table: metricsTable,
|
|
2772
|
+
filter: `workspace_id=eq.${workspaceId}`
|
|
2773
|
+
},
|
|
2774
|
+
async (payload) => {
|
|
2775
|
+
console.log(`Received ${metricsTablePrefix} update:`, payload);
|
|
2776
|
+
await fetchMetrics();
|
|
2777
|
+
}
|
|
2778
|
+
).subscribe((status) => {
|
|
2779
|
+
console.log(`Workspace detailed metrics subscription status:`, status);
|
|
2780
|
+
});
|
|
2781
|
+
}, [supabase, workspaceId, fetchMetrics, schema, metricsTable, metricsTablePrefix]);
|
|
2782
|
+
useEffect(() => {
|
|
2783
|
+
if (!workspaceId) {
|
|
2784
|
+
setIsLoading(false);
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
const channels = [];
|
|
2788
|
+
const operationalDate = date || getOperationalDate();
|
|
2789
|
+
const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
|
|
2790
|
+
const queryShiftId = shiftId ?? currentShift.shiftId;
|
|
2791
|
+
const metricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
|
|
2792
|
+
"postgres_changes",
|
|
2793
|
+
{
|
|
2794
|
+
event: "*",
|
|
2795
|
+
schema,
|
|
2796
|
+
table: metricsTable,
|
|
2797
|
+
filter: `workspace_id=eq.${workspaceId}`
|
|
2798
|
+
},
|
|
2799
|
+
async (payload) => {
|
|
2800
|
+
const payloadData = payload.new;
|
|
2801
|
+
console.log(`Received ${metricsTablePrefix} update:`, {
|
|
2802
|
+
payload,
|
|
2803
|
+
payloadDate: payloadData?.date,
|
|
2804
|
+
payloadShift: payloadData?.shift_id,
|
|
2805
|
+
currentDate: operationalDate,
|
|
2806
|
+
currentShift: queryShiftId,
|
|
2807
|
+
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
2808
|
+
});
|
|
2809
|
+
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
2810
|
+
queueUpdate();
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
).subscribe((status) => {
|
|
2814
|
+
console.log(`${metricsTablePrefix} subscription status:`, status);
|
|
2889
2815
|
});
|
|
2890
|
-
const
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
"Accept": "text/event-stream"
|
|
2816
|
+
const workspaceMetricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
|
|
2817
|
+
"postgres_changes",
|
|
2818
|
+
{
|
|
2819
|
+
event: "*",
|
|
2820
|
+
schema,
|
|
2821
|
+
table: workspaceMetricsBaseTable,
|
|
2822
|
+
filter: `workspace_id=eq.${workspaceId}`
|
|
2898
2823
|
},
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2824
|
+
async (payload) => {
|
|
2825
|
+
const payloadData = payload.new;
|
|
2826
|
+
console.log("Received workspace_metrics update:", {
|
|
2827
|
+
payload,
|
|
2828
|
+
payloadDate: payloadData?.date,
|
|
2829
|
+
payloadShift: payloadData?.shift_id,
|
|
2830
|
+
currentDate: operationalDate,
|
|
2831
|
+
currentShift: queryShiftId,
|
|
2832
|
+
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
2833
|
+
});
|
|
2834
|
+
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
2835
|
+
queueUpdate();
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
).subscribe((status) => {
|
|
2839
|
+
console.log(`Workspace metrics subscription status:`, status);
|
|
2912
2840
|
});
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
}
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2841
|
+
const workspaceActionsChannel = supabase.channel(`workspace-actions-${workspaceId}`).on(
|
|
2842
|
+
"postgres_changes",
|
|
2843
|
+
{
|
|
2844
|
+
event: "*",
|
|
2845
|
+
schema,
|
|
2846
|
+
table: workspaceActionsTable,
|
|
2847
|
+
filter: `workspace_id=eq.${workspaceId}`
|
|
2848
|
+
},
|
|
2849
|
+
async (payload) => {
|
|
2850
|
+
const payloadData = payload.new;
|
|
2851
|
+
console.log("Received workspace_actions update:", {
|
|
2852
|
+
payload,
|
|
2853
|
+
payloadDate: payloadData?.date,
|
|
2854
|
+
payloadShift: payloadData?.shift_id,
|
|
2855
|
+
currentDate: operationalDate,
|
|
2856
|
+
currentShift: queryShiftId,
|
|
2857
|
+
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
2858
|
+
});
|
|
2859
|
+
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
2860
|
+
queueUpdate();
|
|
2925
2861
|
}
|
|
2926
2862
|
}
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
}
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2863
|
+
).subscribe((status) => {
|
|
2864
|
+
console.log(`Workspace actions subscription status:`, status);
|
|
2865
|
+
});
|
|
2866
|
+
channels.push(metricsChannel, workspaceMetricsChannel, workspaceActionsChannel);
|
|
2867
|
+
fetchMetrics();
|
|
2868
|
+
setupSubscription();
|
|
2869
|
+
return () => {
|
|
2870
|
+
if (timeoutRef.current) {
|
|
2871
|
+
clearTimeout(timeoutRef.current);
|
|
2872
|
+
}
|
|
2873
|
+
channels.forEach((channel) => {
|
|
2874
|
+
console.log("Cleaning up channel subscription");
|
|
2875
|
+
supabase.removeChannel(channel);
|
|
2876
|
+
});
|
|
2877
|
+
if (channelRef.current) {
|
|
2878
|
+
supabase.removeChannel(channelRef.current);
|
|
2879
|
+
}
|
|
2880
|
+
};
|
|
2881
|
+
}, [supabase, workspaceId, date, shiftId, fetchMetrics, queueUpdate, setupSubscription, metricsTable, workspaceMetricsBaseTable, workspaceActionsTable, defaultTimezone, shiftConfig, schema, metricsTablePrefix]);
|
|
2882
|
+
return {
|
|
2883
|
+
metrics: metrics2,
|
|
2884
|
+
isLoading,
|
|
2885
|
+
error,
|
|
2886
|
+
refetch: fetchMetrics
|
|
2887
|
+
};
|
|
2888
|
+
};
|
|
2889
|
+
var useLineWorkspaceMetrics = (lineId, options) => {
|
|
2890
|
+
const entityConfig = useEntityConfig();
|
|
2891
|
+
const databaseConfig = useDatabaseConfig();
|
|
2892
|
+
const dateTimeConfig = useDateTimeConfig();
|
|
2893
|
+
const shiftConfig = useShiftConfig();
|
|
2894
|
+
const supabase = useSupabase();
|
|
2895
|
+
const [workspaces, setWorkspaces] = useState([]);
|
|
2896
|
+
const [loading, setLoading] = useState(true);
|
|
2897
|
+
const [error, setError] = useState(null);
|
|
2898
|
+
const [initialized, setInitialized] = useState(false);
|
|
2899
|
+
const queryShiftId = useMemo(() => {
|
|
2900
|
+
const currentShift = getCurrentShift(
|
|
2901
|
+
dateTimeConfig.defaultTimezone || "Asia/Kolkata",
|
|
2902
|
+
shiftConfig
|
|
2903
|
+
);
|
|
2904
|
+
return options?.initialShiftId !== void 0 ? options.initialShiftId : currentShift.shiftId;
|
|
2905
|
+
}, [options?.initialShiftId, dateTimeConfig.defaultTimezone, shiftConfig]);
|
|
2906
|
+
const queryDate = useMemo(() => {
|
|
2907
|
+
return options?.initialDate || getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
2908
|
+
}, [options?.initialDate, dateTimeConfig.defaultTimezone]);
|
|
2909
|
+
const metricsTable = useMemo(() => {
|
|
2910
|
+
const companyId = entityConfig.companyId;
|
|
2911
|
+
if (!companyId) return "";
|
|
2912
|
+
const metricsTablePrefix = getMetricsTablePrefix();
|
|
2913
|
+
return `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2914
|
+
}, [entityConfig.companyId]);
|
|
2915
|
+
const schema = databaseConfig.schema ?? "public";
|
|
2916
|
+
const fetchWorkspaceMetrics = useCallback(async () => {
|
|
2917
|
+
if (!lineId) return;
|
|
2918
|
+
if (!initialized) {
|
|
2919
|
+
setLoading(true);
|
|
2933
2920
|
}
|
|
2921
|
+
setError(null);
|
|
2934
2922
|
try {
|
|
2935
|
-
|
|
2923
|
+
console.log("Fetching workspace metrics with params:", {
|
|
2924
|
+
lineId,
|
|
2925
|
+
queryDate,
|
|
2926
|
+
queryShiftId,
|
|
2927
|
+
metricsTable
|
|
2928
|
+
});
|
|
2929
|
+
const { data, error: fetchError } = await supabase.from(metricsTable).select(`
|
|
2930
|
+
workspace_name,
|
|
2931
|
+
total_output,
|
|
2932
|
+
avg_pph,
|
|
2933
|
+
efficiency,
|
|
2934
|
+
workspace_id,
|
|
2935
|
+
avg_cycle_time,
|
|
2936
|
+
performance_score,
|
|
2937
|
+
trend_score,
|
|
2938
|
+
line_id,
|
|
2939
|
+
total_day_output
|
|
2940
|
+
`).eq("date", queryDate).eq("shift_id", queryShiftId).eq("line_id", lineId).order("workspace_name", { ascending: true });
|
|
2941
|
+
if (fetchError) throw fetchError;
|
|
2942
|
+
const transformedData = (data || []).map((item) => ({
|
|
2943
|
+
company_id: entityConfig.companyId || "unknown",
|
|
2944
|
+
line_id: item.line_id,
|
|
2945
|
+
shift_id: queryShiftId,
|
|
2946
|
+
date: queryDate,
|
|
2947
|
+
workspace_uuid: item.workspace_id,
|
|
2948
|
+
workspace_name: item.workspace_name,
|
|
2949
|
+
action_count: item.total_output || 0,
|
|
2950
|
+
pph: item.avg_pph || 0,
|
|
2951
|
+
performance_score: item.performance_score || 0,
|
|
2952
|
+
avg_cycle_time: item.avg_cycle_time || 0,
|
|
2953
|
+
trend: item.trend_score === 1 ? 2 : 0,
|
|
2954
|
+
predicted_output: 0,
|
|
2955
|
+
efficiency: item.efficiency || 0,
|
|
2956
|
+
action_threshold: item.total_day_output || 0
|
|
2957
|
+
}));
|
|
2958
|
+
setWorkspaces(transformedData);
|
|
2959
|
+
setInitialized(true);
|
|
2960
|
+
} catch (err) {
|
|
2961
|
+
console.error("Error fetching workspace metrics:", err);
|
|
2962
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
2936
2963
|
} finally {
|
|
2937
|
-
|
|
2964
|
+
setLoading(false);
|
|
2938
2965
|
}
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
|
-
if (!
|
|
2942
|
-
|
|
2943
|
-
throw new Error("No response body available for streaming");
|
|
2966
|
+
}, [lineId, queryDate, queryShiftId, metricsTable, supabase, entityConfig.companyId]);
|
|
2967
|
+
useEffect(() => {
|
|
2968
|
+
if (!initialized) {
|
|
2969
|
+
fetchWorkspaceMetrics();
|
|
2944
2970
|
}
|
|
2945
|
-
const
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
const line = lines[i].trim();
|
|
2961
|
-
if (!line) continue;
|
|
2962
|
-
console.log("[SSEClient] Processing line:", line);
|
|
2963
|
-
if (line.startsWith("event:")) {
|
|
2964
|
-
const event = line.slice(6).trim();
|
|
2965
|
-
console.log("[SSEClient] Event type:", event);
|
|
2966
|
-
const nextLine = lines[i + 1];
|
|
2967
|
-
if (nextLine?.startsWith("data:")) {
|
|
2968
|
-
const dataStr = nextLine.slice(5).trim();
|
|
2969
|
-
console.log("[SSEClient] Event data:", dataStr);
|
|
2970
|
-
try {
|
|
2971
|
-
const data = JSON.parse(dataStr);
|
|
2972
|
-
switch (event) {
|
|
2973
|
-
case "thread":
|
|
2974
|
-
callbacks.onThreadCreated?.(data.thread_id);
|
|
2975
|
-
break;
|
|
2976
|
-
case "message":
|
|
2977
|
-
callbacks.onMessage?.(data.text);
|
|
2978
|
-
break;
|
|
2979
|
-
case "reasoning":
|
|
2980
|
-
callbacks.onReasoning?.(data.text);
|
|
2981
|
-
break;
|
|
2982
|
-
case "complete":
|
|
2983
|
-
callbacks.onComplete?.(data.message_id);
|
|
2984
|
-
break;
|
|
2985
|
-
case "error":
|
|
2986
|
-
callbacks.onError?.(data.error);
|
|
2987
|
-
break;
|
|
2988
|
-
}
|
|
2989
|
-
} catch (e) {
|
|
2990
|
-
console.error("[SSEClient] Failed to parse data:", dataStr, e);
|
|
2991
|
-
}
|
|
2992
|
-
i++;
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2971
|
+
const setupSubscription = () => {
|
|
2972
|
+
if (!lineId) return null;
|
|
2973
|
+
const filter2 = `line_id=eq.${lineId} AND date=eq.${queryDate} AND shift_id=eq.${queryShiftId}`;
|
|
2974
|
+
console.log("Setting up subscription with filter:", filter2);
|
|
2975
|
+
const channel2 = supabase.channel(`line-workspace-metrics-${Date.now()}`).on(
|
|
2976
|
+
"postgres_changes",
|
|
2977
|
+
{
|
|
2978
|
+
event: "*",
|
|
2979
|
+
schema,
|
|
2980
|
+
table: metricsTable,
|
|
2981
|
+
filter: filter2
|
|
2982
|
+
},
|
|
2983
|
+
async (payload) => {
|
|
2984
|
+
console.log("Workspace metrics update received:", payload);
|
|
2985
|
+
await fetchWorkspaceMetrics();
|
|
2995
2986
|
}
|
|
2987
|
+
).subscribe();
|
|
2988
|
+
return channel2;
|
|
2989
|
+
};
|
|
2990
|
+
const channel = setupSubscription();
|
|
2991
|
+
return () => {
|
|
2992
|
+
if (channel) {
|
|
2993
|
+
supabase.removeChannel(channel);
|
|
2996
2994
|
}
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2995
|
+
};
|
|
2996
|
+
}, [lineId, queryDate, queryShiftId, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema]);
|
|
2997
|
+
useEffect(() => {
|
|
2998
|
+
setInitialized(false);
|
|
2999
|
+
}, [lineId, queryDate, queryShiftId]);
|
|
3000
|
+
const refreshWorkspaces = fetchWorkspaceMetrics;
|
|
3001
|
+
return useMemo(
|
|
3002
|
+
() => ({ workspaces, loading, error, refreshWorkspaces }),
|
|
3003
|
+
[workspaces, loading, error, refreshWorkspaces]
|
|
3004
|
+
);
|
|
3005
|
+
};
|
|
3006
|
+
var useHistoricWorkspaceMetrics = (workspaceId, date, inputShiftId) => {
|
|
3007
|
+
useSupabase();
|
|
3008
|
+
const [metrics2, setMetrics] = useState(null);
|
|
3009
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
3010
|
+
const [error, setError] = useState(null);
|
|
3011
|
+
const fetchAndAnimateMetrics = useCallback(async () => {
|
|
3012
|
+
if (!workspaceId || !date || inputShiftId === void 0) {
|
|
3013
|
+
setMetrics(null);
|
|
3014
|
+
setIsLoading(false);
|
|
3015
|
+
setError(null);
|
|
3016
|
+
return;
|
|
2999
3017
|
}
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3018
|
+
setIsLoading(true);
|
|
3019
|
+
setError(null);
|
|
3020
|
+
try {
|
|
3021
|
+
const fetchedData = await dashboardService.getWorkspaceDetailedMetrics(
|
|
3022
|
+
workspaceId,
|
|
3023
|
+
date,
|
|
3024
|
+
inputShiftId
|
|
3025
|
+
);
|
|
3026
|
+
if (!fetchedData) {
|
|
3027
|
+
console.warn("No historic data found for workspace:", workspaceId, date, inputShiftId);
|
|
3028
|
+
setMetrics(null);
|
|
3029
|
+
setIsLoading(false);
|
|
3030
|
+
return;
|
|
3008
3031
|
}
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3032
|
+
const initialHourlyCounts = fetchedData.hourly_action_counts && Array.isArray(fetchedData.hourly_action_counts) ? new Array(fetchedData.hourly_action_counts.length).fill(0) : [];
|
|
3033
|
+
setMetrics({
|
|
3034
|
+
...fetchedData,
|
|
3035
|
+
hourly_action_counts: initialHourlyCounts
|
|
3036
|
+
});
|
|
3037
|
+
setIsLoading(false);
|
|
3038
|
+
if (fetchedData.hourly_action_counts && fetchedData.hourly_action_counts.length > 0) {
|
|
3039
|
+
const totalSteps = 60;
|
|
3040
|
+
let currentStep = 0;
|
|
3041
|
+
let animationIntervalId = setInterval(() => {
|
|
3042
|
+
currentStep++;
|
|
3043
|
+
const progress6 = currentStep / totalSteps;
|
|
3044
|
+
setMetrics((prevMetrics) => {
|
|
3045
|
+
const currentHourlyData = (fetchedData.hourly_action_counts || []).map(
|
|
3046
|
+
(target) => Math.round(target * progress6)
|
|
3047
|
+
);
|
|
3048
|
+
return {
|
|
3049
|
+
...fetchedData,
|
|
3050
|
+
// Base with all other correct data from the latest fetch
|
|
3051
|
+
hourly_action_counts: currentHourlyData
|
|
3052
|
+
};
|
|
3053
|
+
});
|
|
3054
|
+
if (currentStep >= totalSteps) {
|
|
3055
|
+
if (animationIntervalId) clearInterval(animationIntervalId);
|
|
3056
|
+
setMetrics(fetchedData);
|
|
3057
|
+
}
|
|
3058
|
+
}, 1e3 / 60);
|
|
3059
|
+
} else {
|
|
3060
|
+
setMetrics(fetchedData);
|
|
3012
3061
|
}
|
|
3013
|
-
|
|
3062
|
+
} catch (err) {
|
|
3063
|
+
console.error("Error fetching historic workspace metrics:", err);
|
|
3064
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
3065
|
+
setIsLoading(false);
|
|
3066
|
+
setMetrics(null);
|
|
3014
3067
|
}
|
|
3015
|
-
}
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
}
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
return data;
|
|
3053
|
-
}
|
|
3054
|
-
async function updateThreadTitle(threadId, newTitle) {
|
|
3055
|
-
const supabase = _getSupabaseInstance();
|
|
3056
|
-
const { data, error } = await supabase.schema("ai").from("chat_threads").update({
|
|
3057
|
-
title: newTitle,
|
|
3058
|
-
auto_title: false,
|
|
3059
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3060
|
-
}).eq("id", threadId).select().single();
|
|
3061
|
-
if (error) throw error;
|
|
3062
|
-
return data;
|
|
3063
|
-
}
|
|
3064
|
-
async function deleteThread(threadId) {
|
|
3065
|
-
const supabase = _getSupabaseInstance();
|
|
3066
|
-
const { error } = await supabase.schema("ai").from("chat_threads").delete().eq("id", threadId);
|
|
3067
|
-
if (error) throw error;
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
// src/lib/hooks/useLineDetailedMetrics.ts
|
|
3068
|
+
}, [workspaceId, date, inputShiftId]);
|
|
3069
|
+
useEffect(() => {
|
|
3070
|
+
fetchAndAnimateMetrics();
|
|
3071
|
+
}, [fetchAndAnimateMetrics]);
|
|
3072
|
+
const refetch = useCallback(async () => {
|
|
3073
|
+
if (!workspaceId || !date || inputShiftId === void 0) {
|
|
3074
|
+
setError(null);
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
setIsLoading(true);
|
|
3078
|
+
setError(null);
|
|
3079
|
+
try {
|
|
3080
|
+
const fetchedData = await dashboardService.getWorkspaceDetailedMetrics(
|
|
3081
|
+
workspaceId,
|
|
3082
|
+
date,
|
|
3083
|
+
inputShiftId
|
|
3084
|
+
);
|
|
3085
|
+
if (!fetchedData) {
|
|
3086
|
+
setMetrics(null);
|
|
3087
|
+
return;
|
|
3088
|
+
}
|
|
3089
|
+
setMetrics(fetchedData);
|
|
3090
|
+
} catch (err) {
|
|
3091
|
+
console.error("Error re-fetching historic workspace metrics:", err);
|
|
3092
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
3093
|
+
setMetrics(null);
|
|
3094
|
+
} finally {
|
|
3095
|
+
setIsLoading(false);
|
|
3096
|
+
}
|
|
3097
|
+
}, [workspaceId, date, inputShiftId]);
|
|
3098
|
+
return {
|
|
3099
|
+
metrics: metrics2,
|
|
3100
|
+
isLoading,
|
|
3101
|
+
error,
|
|
3102
|
+
refetch
|
|
3103
|
+
};
|
|
3104
|
+
};
|
|
3071
3105
|
var useLineDetailedMetrics = (lineIdFromProp) => {
|
|
3072
3106
|
const entityConfig = useEntityConfig();
|
|
3073
3107
|
const databaseConfig = useDatabaseConfig();
|
|
@@ -4035,166 +4069,511 @@ var useTargets = (options) => {
|
|
|
4035
4069
|
setIsLoading(false);
|
|
4036
4070
|
return;
|
|
4037
4071
|
}
|
|
4038
|
-
if (!companyId) {
|
|
4039
|
-
setError({ message: "Company ID not configured.", code: "CONFIG_ERROR" });
|
|
4040
|
-
setIsLoading(false);
|
|
4041
|
-
setTargets([]);
|
|
4042
|
-
return;
|
|
4072
|
+
if (!companyId) {
|
|
4073
|
+
setError({ message: "Company ID not configured.", code: "CONFIG_ERROR" });
|
|
4074
|
+
setIsLoading(false);
|
|
4075
|
+
setTargets([]);
|
|
4076
|
+
return;
|
|
4077
|
+
}
|
|
4078
|
+
setIsLoading(true);
|
|
4079
|
+
setError(null);
|
|
4080
|
+
try {
|
|
4081
|
+
let query = supabase.from(targetsTable).select("*");
|
|
4082
|
+
query = query.eq("company_id", companyId);
|
|
4083
|
+
if (options?.entityId) {
|
|
4084
|
+
query = query.eq("entity_id", options.entityId);
|
|
4085
|
+
}
|
|
4086
|
+
if (options?.entityType) {
|
|
4087
|
+
query = query.eq("entity_type", options.entityType);
|
|
4088
|
+
}
|
|
4089
|
+
if (options?.date) {
|
|
4090
|
+
query = query.eq("date", options.date);
|
|
4091
|
+
}
|
|
4092
|
+
if (options?.shiftId !== void 0) {
|
|
4093
|
+
query = query.eq("shift_id", options.shiftId);
|
|
4094
|
+
}
|
|
4095
|
+
if (options?.period) {
|
|
4096
|
+
query = query.eq("period", options.period);
|
|
4097
|
+
}
|
|
4098
|
+
query = query.order("createdAt", { ascending: false });
|
|
4099
|
+
const { data, error: fetchError } = await query;
|
|
4100
|
+
if (fetchError) throw fetchError;
|
|
4101
|
+
setTargets(data || []);
|
|
4102
|
+
} catch (err) {
|
|
4103
|
+
console.error("[useTargets] Error fetching targets:", err);
|
|
4104
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
4105
|
+
setTargets([]);
|
|
4106
|
+
} finally {
|
|
4107
|
+
setIsLoading(false);
|
|
4108
|
+
}
|
|
4109
|
+
}, [supabase, companyId, targetsTable, options]);
|
|
4110
|
+
useEffect(() => {
|
|
4111
|
+
fetchData();
|
|
4112
|
+
}, [fetchData]);
|
|
4113
|
+
return {
|
|
4114
|
+
targets,
|
|
4115
|
+
isLoading,
|
|
4116
|
+
error,
|
|
4117
|
+
refetch: fetchData
|
|
4118
|
+
};
|
|
4119
|
+
};
|
|
4120
|
+
var DEFAULT_SHIFTS_TABLE_NAME = "shift_configurations";
|
|
4121
|
+
var useShifts = () => {
|
|
4122
|
+
const { supabaseUrl, supabaseKey } = useDashboardConfig();
|
|
4123
|
+
const { companyId } = useEntityConfig();
|
|
4124
|
+
const { tables } = useDatabaseConfig();
|
|
4125
|
+
const [shifts, setShifts] = useState([]);
|
|
4126
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
4127
|
+
const [error, setError] = useState(null);
|
|
4128
|
+
const supabase = useMemo(() => {
|
|
4129
|
+
if (!supabaseUrl || !supabaseKey) return null;
|
|
4130
|
+
return createClient(supabaseUrl, supabaseKey);
|
|
4131
|
+
}, [supabaseUrl, supabaseKey]);
|
|
4132
|
+
const shiftsTable = tables?.shiftConfigurations || DEFAULT_SHIFTS_TABLE_NAME;
|
|
4133
|
+
const fetchData = useCallback(async () => {
|
|
4134
|
+
if (!supabase) {
|
|
4135
|
+
setError({ message: "Supabase client not initialized.", code: "CLIENT_INIT_ERROR" });
|
|
4136
|
+
setIsLoading(false);
|
|
4137
|
+
return;
|
|
4138
|
+
}
|
|
4139
|
+
if (!companyId) {
|
|
4140
|
+
setError({ message: "Company ID not configured.", code: "CONFIG_ERROR" });
|
|
4141
|
+
setIsLoading(false);
|
|
4142
|
+
setShifts([]);
|
|
4143
|
+
return;
|
|
4144
|
+
}
|
|
4145
|
+
setIsLoading(true);
|
|
4146
|
+
setError(null);
|
|
4147
|
+
try {
|
|
4148
|
+
let query = supabase.from(shiftsTable).select("*");
|
|
4149
|
+
query = query.eq("company_id", companyId);
|
|
4150
|
+
query = query.order("startTime", { ascending: true });
|
|
4151
|
+
const { data, error: fetchError } = await query;
|
|
4152
|
+
if (fetchError) throw fetchError;
|
|
4153
|
+
setShifts(data || []);
|
|
4154
|
+
} catch (err) {
|
|
4155
|
+
console.error("[useShifts] Error fetching shift configurations:", err);
|
|
4156
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
4157
|
+
setShifts([]);
|
|
4158
|
+
} finally {
|
|
4159
|
+
setIsLoading(false);
|
|
4160
|
+
}
|
|
4161
|
+
}, [supabase, companyId, shiftsTable]);
|
|
4162
|
+
useEffect(() => {
|
|
4163
|
+
fetchData();
|
|
4164
|
+
}, [fetchData]);
|
|
4165
|
+
return {
|
|
4166
|
+
shifts,
|
|
4167
|
+
isLoading,
|
|
4168
|
+
error,
|
|
4169
|
+
refetch: fetchData
|
|
4170
|
+
};
|
|
4171
|
+
};
|
|
4172
|
+
var DEFAULT_OPERATORS_TABLE_NAME = "workspace_operator_assignments";
|
|
4173
|
+
var useWorkspaceOperators = (workspaceId, options) => {
|
|
4174
|
+
const { companyId } = useEntityConfig();
|
|
4175
|
+
const supabase = useSupabase();
|
|
4176
|
+
const [operators, setOperators] = useState([]);
|
|
4177
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
4178
|
+
const [error, setError] = useState(null);
|
|
4179
|
+
const operatorsTable = DEFAULT_OPERATORS_TABLE_NAME;
|
|
4180
|
+
const fetchData = useCallback(async () => {
|
|
4181
|
+
if (!workspaceId) {
|
|
4182
|
+
setError({ message: "Workspace ID is required.", code: "MISSING_PARAM" });
|
|
4183
|
+
setIsLoading(false);
|
|
4184
|
+
setOperators([]);
|
|
4185
|
+
return;
|
|
4186
|
+
}
|
|
4187
|
+
if (!supabase) {
|
|
4188
|
+
setError({ message: "Supabase client not initialized.", code: "CLIENT_INIT_ERROR" });
|
|
4189
|
+
setIsLoading(false);
|
|
4190
|
+
return;
|
|
4191
|
+
}
|
|
4192
|
+
if (!companyId) {
|
|
4193
|
+
setError({ message: "Company ID not configured.", code: "CONFIG_ERROR" });
|
|
4194
|
+
setIsLoading(false);
|
|
4195
|
+
setOperators([]);
|
|
4196
|
+
return;
|
|
4197
|
+
}
|
|
4198
|
+
setIsLoading(true);
|
|
4199
|
+
setError(null);
|
|
4200
|
+
try {
|
|
4201
|
+
let query = supabase.from(operatorsTable).select("*");
|
|
4202
|
+
query = query.eq("company_id", companyId);
|
|
4203
|
+
query = query.eq("workspace_id", workspaceId);
|
|
4204
|
+
if (options?.date) {
|
|
4205
|
+
query = query.eq("date", options.date);
|
|
4206
|
+
}
|
|
4207
|
+
if (options?.shiftId !== void 0) {
|
|
4208
|
+
query = query.eq("shift_id", options.shiftId);
|
|
4209
|
+
}
|
|
4210
|
+
query = query.order("createdAt", { ascending: false });
|
|
4211
|
+
const { data, error: fetchError } = await query;
|
|
4212
|
+
if (fetchError) throw fetchError;
|
|
4213
|
+
setOperators(data || []);
|
|
4214
|
+
} catch (err) {
|
|
4215
|
+
console.error("[useWorkspaceOperators] Error fetching operators:", err);
|
|
4216
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
4217
|
+
setOperators([]);
|
|
4218
|
+
} finally {
|
|
4219
|
+
setIsLoading(false);
|
|
4220
|
+
}
|
|
4221
|
+
}, [supabase, companyId, workspaceId, operatorsTable, options]);
|
|
4222
|
+
useEffect(() => {
|
|
4223
|
+
fetchData();
|
|
4224
|
+
}, [fetchData]);
|
|
4225
|
+
return {
|
|
4226
|
+
operators,
|
|
4227
|
+
isLoading,
|
|
4228
|
+
error,
|
|
4229
|
+
refetch: fetchData
|
|
4230
|
+
};
|
|
4231
|
+
};
|
|
4232
|
+
|
|
4233
|
+
// src/lib/utils/dashboardReload.ts
|
|
4234
|
+
var createThrottledReload = (interval = 5e3) => {
|
|
4235
|
+
let last = 0;
|
|
4236
|
+
let queued = false;
|
|
4237
|
+
const doReload = () => {
|
|
4238
|
+
if (typeof window !== "undefined") {
|
|
4239
|
+
window.location.reload();
|
|
4240
|
+
}
|
|
4241
|
+
};
|
|
4242
|
+
return () => {
|
|
4243
|
+
const now2 = Date.now();
|
|
4244
|
+
if (now2 - last >= interval) {
|
|
4245
|
+
last = now2;
|
|
4246
|
+
doReload();
|
|
4247
|
+
} else if (!queued) {
|
|
4248
|
+
queued = true;
|
|
4249
|
+
setTimeout(() => {
|
|
4250
|
+
queued = false;
|
|
4251
|
+
last = Date.now();
|
|
4252
|
+
doReload();
|
|
4253
|
+
}, interval - (now2 - last));
|
|
4254
|
+
}
|
|
4255
|
+
};
|
|
4256
|
+
};
|
|
4257
|
+
var throttledReloadDashboard = createThrottledReload(5e3);
|
|
4258
|
+
|
|
4259
|
+
// src/lib/hooks/useHlsStream.ts
|
|
4260
|
+
var HLS_CONFIG = {
|
|
4261
|
+
maxBufferLength: 8,
|
|
4262
|
+
maxMaxBufferLength: 15,
|
|
4263
|
+
lowLatencyMode: false,
|
|
4264
|
+
enableWorker: true,
|
|
4265
|
+
// Retry + timeout tuned for quick recovery
|
|
4266
|
+
manifestLoadingMaxRetry: 4,
|
|
4267
|
+
levelLoadingMaxRetry: 3,
|
|
4268
|
+
fragLoadingMaxRetry: 4,
|
|
4269
|
+
manifestLoadingRetryDelay: 500,
|
|
4270
|
+
levelLoadingRetryDelay: 500,
|
|
4271
|
+
fragLoadingRetryDelay: 500,
|
|
4272
|
+
manifestLoadingTimeOut: 1e4,
|
|
4273
|
+
levelLoadingTimeOut: 8e3,
|
|
4274
|
+
fragLoadingTimeOut: 1e4,
|
|
4275
|
+
liveSyncDurationCount: 2
|
|
4276
|
+
// Follow live edge aggressively
|
|
4277
|
+
};
|
|
4278
|
+
function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
|
|
4279
|
+
const [restartKey, setRestartKey] = useState(0);
|
|
4280
|
+
const hlsRef = useRef(null);
|
|
4281
|
+
const stallCheckIntervalRef = useRef(null);
|
|
4282
|
+
const noProgressTimerRef = useRef(null);
|
|
4283
|
+
const lastTimeUpdateRef = useRef(0);
|
|
4284
|
+
const softRestartCountRef = useRef(0);
|
|
4285
|
+
const isNativeHlsRef = useRef(false);
|
|
4286
|
+
const waitingTimerRef = useRef(null);
|
|
4287
|
+
const cleanup = () => {
|
|
4288
|
+
if (stallCheckIntervalRef.current) {
|
|
4289
|
+
clearInterval(stallCheckIntervalRef.current);
|
|
4290
|
+
stallCheckIntervalRef.current = null;
|
|
4291
|
+
}
|
|
4292
|
+
if (noProgressTimerRef.current) {
|
|
4293
|
+
clearTimeout(noProgressTimerRef.current);
|
|
4294
|
+
noProgressTimerRef.current = null;
|
|
4295
|
+
}
|
|
4296
|
+
if (waitingTimerRef.current) {
|
|
4297
|
+
clearTimeout(waitingTimerRef.current);
|
|
4298
|
+
waitingTimerRef.current = null;
|
|
4299
|
+
}
|
|
4300
|
+
if (hlsRef.current) {
|
|
4301
|
+
hlsRef.current.destroy();
|
|
4302
|
+
hlsRef.current = null;
|
|
4303
|
+
}
|
|
4304
|
+
const video = videoRef.current;
|
|
4305
|
+
if (video) {
|
|
4306
|
+
video.pause();
|
|
4307
|
+
video.removeAttribute("src");
|
|
4308
|
+
video.load();
|
|
4309
|
+
video.removeEventListener("waiting", handleWaiting);
|
|
4310
|
+
video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
4311
|
+
video.removeEventListener("error", handleNativeError);
|
|
4312
|
+
}
|
|
4313
|
+
lastTimeUpdateRef.current = 0;
|
|
4314
|
+
softRestartCountRef.current = 0;
|
|
4315
|
+
};
|
|
4316
|
+
const seekToLiveEdge = () => {
|
|
4317
|
+
const hls = hlsRef.current;
|
|
4318
|
+
const video = videoRef.current;
|
|
4319
|
+
if (!hls || !video) return;
|
|
4320
|
+
if (hls.liveSyncPosition !== null && hls.liveSyncPosition !== void 0) {
|
|
4321
|
+
video.currentTime = hls.liveSyncPosition;
|
|
4322
|
+
} else if (hls.levels?.[hls.currentLevel]?.details) {
|
|
4323
|
+
const levelDetails = hls.levels[hls.currentLevel].details;
|
|
4324
|
+
if (levelDetails && levelDetails.edge !== void 0) {
|
|
4325
|
+
video.currentTime = Math.max(0, levelDetails.edge - 5);
|
|
4326
|
+
}
|
|
4043
4327
|
}
|
|
4044
|
-
|
|
4045
|
-
|
|
4328
|
+
};
|
|
4329
|
+
const softRestart = (reason) => {
|
|
4330
|
+
console.warn(`[HLS] Soft restart: ${reason}`);
|
|
4331
|
+
const hls = hlsRef.current;
|
|
4332
|
+
if (!hls) return;
|
|
4046
4333
|
try {
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
query = query.eq("entity_type", options.entityType);
|
|
4054
|
-
}
|
|
4055
|
-
if (options?.date) {
|
|
4056
|
-
query = query.eq("date", options.date);
|
|
4057
|
-
}
|
|
4058
|
-
if (options?.shiftId !== void 0) {
|
|
4059
|
-
query = query.eq("shift_id", options.shiftId);
|
|
4334
|
+
hls.stopLoad();
|
|
4335
|
+
hls.startLoad(-1);
|
|
4336
|
+
seekToLiveEdge();
|
|
4337
|
+
softRestartCountRef.current++;
|
|
4338
|
+
if (softRestartCountRef.current >= 5) {
|
|
4339
|
+
hardRestart(`${reason} (escalated after ${softRestartCountRef.current} soft restarts)`);
|
|
4060
4340
|
}
|
|
4061
|
-
|
|
4062
|
-
|
|
4341
|
+
} catch (error) {
|
|
4342
|
+
console.error("[HLS] Soft restart failed:", error);
|
|
4343
|
+
hardRestart(`${reason} (soft restart error)`);
|
|
4344
|
+
}
|
|
4345
|
+
};
|
|
4346
|
+
const hardRestart = (reason) => {
|
|
4347
|
+
console.warn(`[HLS] Hard restart: ${reason}`);
|
|
4348
|
+
cleanup();
|
|
4349
|
+
setRestartKey((k) => k + 1);
|
|
4350
|
+
softRestartCountRef.current = 0;
|
|
4351
|
+
if (reason.includes("hard restart") || reason.includes("native video error")) {
|
|
4352
|
+
if (onFatalError) {
|
|
4353
|
+
onFatalError();
|
|
4354
|
+
} else {
|
|
4355
|
+
throttledReloadDashboard();
|
|
4063
4356
|
}
|
|
4064
|
-
query = query.order("createdAt", { ascending: false });
|
|
4065
|
-
const { data, error: fetchError } = await query;
|
|
4066
|
-
if (fetchError) throw fetchError;
|
|
4067
|
-
setTargets(data || []);
|
|
4068
|
-
} catch (err) {
|
|
4069
|
-
console.error("[useTargets] Error fetching targets:", err);
|
|
4070
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
4071
|
-
setTargets([]);
|
|
4072
|
-
} finally {
|
|
4073
|
-
setIsLoading(false);
|
|
4074
4357
|
}
|
|
4075
|
-
}, [supabase, companyId, targetsTable, options]);
|
|
4076
|
-
useEffect(() => {
|
|
4077
|
-
fetchData();
|
|
4078
|
-
}, [fetchData]);
|
|
4079
|
-
return {
|
|
4080
|
-
targets,
|
|
4081
|
-
isLoading,
|
|
4082
|
-
error,
|
|
4083
|
-
refetch: fetchData
|
|
4084
4358
|
};
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
const { tables } = useDatabaseConfig();
|
|
4091
|
-
const [shifts, setShifts] = useState([]);
|
|
4092
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
4093
|
-
const [error, setError] = useState(null);
|
|
4094
|
-
const supabase = useMemo(() => {
|
|
4095
|
-
if (!supabaseUrl || !supabaseKey) return null;
|
|
4096
|
-
return createClient(supabaseUrl, supabaseKey);
|
|
4097
|
-
}, [supabaseUrl, supabaseKey]);
|
|
4098
|
-
const shiftsTable = tables?.shiftConfigurations || DEFAULT_SHIFTS_TABLE_NAME;
|
|
4099
|
-
const fetchData = useCallback(async () => {
|
|
4100
|
-
if (!supabase) {
|
|
4101
|
-
setError({ message: "Supabase client not initialized.", code: "CLIENT_INIT_ERROR" });
|
|
4102
|
-
setIsLoading(false);
|
|
4103
|
-
return;
|
|
4359
|
+
const handleWaiting = () => {
|
|
4360
|
+
if (isNativeHlsRef.current) return;
|
|
4361
|
+
console.log("[HLS] Video waiting (buffer underrun)");
|
|
4362
|
+
if (waitingTimerRef.current) {
|
|
4363
|
+
clearTimeout(waitingTimerRef.current);
|
|
4104
4364
|
}
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4365
|
+
waitingTimerRef.current = setTimeout(() => {
|
|
4366
|
+
const video = videoRef.current;
|
|
4367
|
+
if (video && video.readyState < 3) {
|
|
4368
|
+
softRestart("waiting timeout");
|
|
4369
|
+
}
|
|
4370
|
+
}, 1e4);
|
|
4371
|
+
};
|
|
4372
|
+
const handleTimeUpdate = () => {
|
|
4373
|
+
const video = videoRef.current;
|
|
4374
|
+
if (!video) return;
|
|
4375
|
+
lastTimeUpdateRef.current = video.currentTime;
|
|
4376
|
+
if (waitingTimerRef.current) {
|
|
4377
|
+
clearTimeout(waitingTimerRef.current);
|
|
4378
|
+
waitingTimerRef.current = null;
|
|
4379
|
+
}
|
|
4380
|
+
};
|
|
4381
|
+
const handleNativeError = () => {
|
|
4382
|
+
console.error("[HLS] Native video error");
|
|
4383
|
+
hardRestart("native video error");
|
|
4384
|
+
};
|
|
4385
|
+
const startStallDetection = () => {
|
|
4386
|
+
if (isNativeHlsRef.current) return;
|
|
4387
|
+
stallCheckIntervalRef.current = setInterval(() => {
|
|
4388
|
+
const video = videoRef.current;
|
|
4389
|
+
if (!video || video.paused || video.ended) return;
|
|
4390
|
+
const currentTime = video.currentTime;
|
|
4391
|
+
const lastTime = lastTimeUpdateRef.current;
|
|
4392
|
+
if (Math.abs(currentTime - lastTime) < 0.1 && video.readyState >= 2) {
|
|
4393
|
+
console.warn("[HLS] Playback stall detected");
|
|
4394
|
+
if (!noProgressTimerRef.current) {
|
|
4395
|
+
noProgressTimerRef.current = setTimeout(() => {
|
|
4396
|
+
softRestart("playback stall");
|
|
4397
|
+
noProgressTimerRef.current = null;
|
|
4398
|
+
}, 8e3);
|
|
4399
|
+
}
|
|
4400
|
+
} else {
|
|
4401
|
+
if (noProgressTimerRef.current) {
|
|
4402
|
+
clearTimeout(noProgressTimerRef.current);
|
|
4403
|
+
noProgressTimerRef.current = null;
|
|
4404
|
+
}
|
|
4405
|
+
}
|
|
4406
|
+
}, 7e3);
|
|
4407
|
+
};
|
|
4408
|
+
useEffect(() => {
|
|
4409
|
+
if (!src || !shouldPlay) {
|
|
4410
|
+
cleanup();
|
|
4109
4411
|
return;
|
|
4110
4412
|
}
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4413
|
+
const video = videoRef.current;
|
|
4414
|
+
if (!video) return;
|
|
4415
|
+
isNativeHlsRef.current = video.canPlayType("application/vnd.apple.mpegurl") === "probably";
|
|
4416
|
+
if (Hls2.isSupported() && !isNativeHlsRef.current) {
|
|
4417
|
+
const hls = new Hls2(HLS_CONFIG);
|
|
4418
|
+
hlsRef.current = hls;
|
|
4419
|
+
hls.attachMedia(video);
|
|
4420
|
+
hls.loadSource(src);
|
|
4421
|
+
hls.on(Hls2.Events.ERROR, (_, data) => {
|
|
4422
|
+
if (!data.fatal) return;
|
|
4423
|
+
console.error("[HLS] Fatal error:", data.type, data.details);
|
|
4424
|
+
if (data.response?.code === 404) {
|
|
4425
|
+
hardRestart("404 hard restart");
|
|
4426
|
+
return;
|
|
4427
|
+
}
|
|
4428
|
+
switch (data.type) {
|
|
4429
|
+
case Hls2.ErrorTypes.NETWORK_ERROR:
|
|
4430
|
+
case Hls2.ErrorTypes.MEDIA_ERROR:
|
|
4431
|
+
softRestart(`${data.type}: ${data.details}`);
|
|
4432
|
+
break;
|
|
4433
|
+
default:
|
|
4434
|
+
hardRestart(`Fatal ${data.type}: ${data.details}`);
|
|
4435
|
+
break;
|
|
4436
|
+
}
|
|
4437
|
+
});
|
|
4438
|
+
hls.on(Hls2.Events.MANIFEST_PARSED, () => {
|
|
4439
|
+
video.play().catch((err) => {
|
|
4440
|
+
console.error("[HLS] Play failed:", err);
|
|
4441
|
+
});
|
|
4442
|
+
});
|
|
4443
|
+
video.addEventListener("waiting", handleWaiting);
|
|
4444
|
+
video.addEventListener("timeupdate", handleTimeUpdate);
|
|
4445
|
+
startStallDetection();
|
|
4446
|
+
} else if (isNativeHlsRef.current) {
|
|
4447
|
+
console.log("[HLS] Using native HLS");
|
|
4448
|
+
video.src = src;
|
|
4449
|
+
video.addEventListener("error", handleNativeError);
|
|
4450
|
+
video.play().catch((err) => {
|
|
4451
|
+
console.error("[HLS] Native play failed:", err);
|
|
4452
|
+
});
|
|
4453
|
+
} else {
|
|
4454
|
+
console.error("[HLS] HLS not supported");
|
|
4126
4455
|
}
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
fetchData();
|
|
4130
|
-
}, [fetchData]);
|
|
4456
|
+
return cleanup;
|
|
4457
|
+
}, [src, shouldPlay, restartKey, onFatalError]);
|
|
4131
4458
|
return {
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
error,
|
|
4135
|
-
refetch: fetchData
|
|
4459
|
+
restartKey,
|
|
4460
|
+
isNativeHls: isNativeHlsRef.current
|
|
4136
4461
|
};
|
|
4137
|
-
}
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
const
|
|
4141
|
-
const
|
|
4142
|
-
const
|
|
4143
|
-
const
|
|
4144
|
-
const
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4462
|
+
}
|
|
4463
|
+
function useHlsStreamWithCropping(videoRef, canvasRef, options) {
|
|
4464
|
+
const { src, shouldPlay, cropping, canvasFps = 30, useRAF = true, onFatalError } = options;
|
|
4465
|
+
const animationFrameRef = useRef(null);
|
|
4466
|
+
const intervalRef = useRef(null);
|
|
4467
|
+
const isDrawingRef = useRef(false);
|
|
4468
|
+
const hlsState = useHlsStream(videoRef, { src, shouldPlay, onFatalError });
|
|
4469
|
+
const calculateCropRect = useCallback((video, cropping2) => {
|
|
4470
|
+
const videoWidth = video.videoWidth;
|
|
4471
|
+
const videoHeight = video.videoHeight;
|
|
4472
|
+
const sx = cropping2.x / 100 * videoWidth;
|
|
4473
|
+
const sy = cropping2.y / 100 * videoHeight;
|
|
4474
|
+
const sw = cropping2.width / 100 * videoWidth;
|
|
4475
|
+
const sh = cropping2.height / 100 * videoHeight;
|
|
4476
|
+
return { sx, sy, sw, sh };
|
|
4477
|
+
}, []);
|
|
4478
|
+
const drawFrame = useCallback(() => {
|
|
4479
|
+
const video = videoRef.current;
|
|
4480
|
+
const canvas = canvasRef.current;
|
|
4481
|
+
if (!video || !canvas || !cropping) return;
|
|
4482
|
+
const ctx = canvas.getContext("2d");
|
|
4483
|
+
if (!ctx) return;
|
|
4484
|
+
if (video.readyState < 2) return;
|
|
4485
|
+
try {
|
|
4486
|
+
const videoWidth = video.videoWidth;
|
|
4487
|
+
const videoHeight = video.videoHeight;
|
|
4488
|
+
if (!videoWidth || !videoHeight) return;
|
|
4489
|
+
const { sx, sy, sw, sh } = calculateCropRect(video, cropping);
|
|
4490
|
+
const canvasContainer = canvas.parentElement;
|
|
4491
|
+
if (canvasContainer) {
|
|
4492
|
+
const containerWidth = canvasContainer.clientWidth;
|
|
4493
|
+
const containerHeight = canvasContainer.clientHeight;
|
|
4494
|
+
if (canvas.width !== containerWidth || canvas.height !== containerHeight) {
|
|
4495
|
+
canvas.width = containerWidth;
|
|
4496
|
+
canvas.height = containerHeight;
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
4500
|
+
ctx.drawImage(
|
|
4501
|
+
video,
|
|
4502
|
+
sx,
|
|
4503
|
+
sy,
|
|
4504
|
+
sw,
|
|
4505
|
+
sh,
|
|
4506
|
+
// Source rectangle (cropped portion)
|
|
4507
|
+
0,
|
|
4508
|
+
0,
|
|
4509
|
+
canvas.width,
|
|
4510
|
+
canvas.height
|
|
4511
|
+
// Destination rectangle (full canvas)
|
|
4512
|
+
);
|
|
4513
|
+
} catch (err) {
|
|
4514
|
+
console.warn("Canvas drawing error:", err);
|
|
4515
|
+
}
|
|
4516
|
+
}, [videoRef, canvasRef, cropping, calculateCropRect]);
|
|
4517
|
+
const startCanvasRendering = useCallback(() => {
|
|
4518
|
+
if (isDrawingRef.current) return;
|
|
4519
|
+
isDrawingRef.current = true;
|
|
4520
|
+
if (useRAF) {
|
|
4521
|
+
const animate = () => {
|
|
4522
|
+
drawFrame();
|
|
4523
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
4524
|
+
};
|
|
4525
|
+
animate();
|
|
4526
|
+
} else {
|
|
4527
|
+
const frameInterval = 1e3 / canvasFps;
|
|
4528
|
+
intervalRef.current = setInterval(drawFrame, frameInterval);
|
|
4152
4529
|
}
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4530
|
+
}, [drawFrame, canvasFps, useRAF]);
|
|
4531
|
+
const stopCanvasRendering = useCallback(() => {
|
|
4532
|
+
isDrawingRef.current = false;
|
|
4533
|
+
if (animationFrameRef.current) {
|
|
4534
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
4535
|
+
animationFrameRef.current = null;
|
|
4157
4536
|
}
|
|
4158
|
-
if (
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4537
|
+
if (intervalRef.current) {
|
|
4538
|
+
clearInterval(intervalRef.current);
|
|
4539
|
+
intervalRef.current = null;
|
|
4540
|
+
}
|
|
4541
|
+
}, []);
|
|
4542
|
+
useEffect(() => {
|
|
4543
|
+
const video = videoRef.current;
|
|
4544
|
+
if (!video || !cropping || !shouldPlay) {
|
|
4545
|
+
stopCanvasRendering();
|
|
4162
4546
|
return;
|
|
4163
4547
|
}
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
let query = supabase.from(operatorsTable).select("*");
|
|
4168
|
-
query = query.eq("company_id", companyId);
|
|
4169
|
-
query = query.eq("workspace_id", workspaceId);
|
|
4170
|
-
if (options?.date) {
|
|
4171
|
-
query = query.eq("date", options.date);
|
|
4172
|
-
}
|
|
4173
|
-
if (options?.shiftId !== void 0) {
|
|
4174
|
-
query = query.eq("shift_id", options.shiftId);
|
|
4548
|
+
const handlePlay = () => {
|
|
4549
|
+
if (cropping) {
|
|
4550
|
+
startCanvasRendering();
|
|
4175
4551
|
}
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4552
|
+
};
|
|
4553
|
+
const handlePause = () => {
|
|
4554
|
+
stopCanvasRendering();
|
|
4555
|
+
};
|
|
4556
|
+
const handleEnded = () => {
|
|
4557
|
+
stopCanvasRendering();
|
|
4558
|
+
};
|
|
4559
|
+
video.addEventListener("play", handlePlay);
|
|
4560
|
+
video.addEventListener("pause", handlePause);
|
|
4561
|
+
video.addEventListener("ended", handleEnded);
|
|
4562
|
+
if (!video.paused && video.readyState >= 2) {
|
|
4563
|
+
startCanvasRendering();
|
|
4186
4564
|
}
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4565
|
+
return () => {
|
|
4566
|
+
stopCanvasRendering();
|
|
4567
|
+
video.removeEventListener("play", handlePlay);
|
|
4568
|
+
video.removeEventListener("pause", handlePause);
|
|
4569
|
+
video.removeEventListener("ended", handleEnded);
|
|
4570
|
+
};
|
|
4571
|
+
}, [videoRef, cropping, shouldPlay, startCanvasRendering, stopCanvasRendering]);
|
|
4191
4572
|
return {
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
error,
|
|
4195
|
-
refetch: fetchData
|
|
4573
|
+
...hlsState,
|
|
4574
|
+
isCanvasRendering: isDrawingRef.current
|
|
4196
4575
|
};
|
|
4197
|
-
}
|
|
4576
|
+
}
|
|
4198
4577
|
function useThreads() {
|
|
4199
4578
|
const supabase = _getSupabaseInstance();
|
|
4200
4579
|
const fetcher = async (key) => {
|
|
@@ -4854,347 +5233,116 @@ function useNavigation(customNavigate) {
|
|
|
4854
5233
|
},
|
|
4855
5234
|
[router, customNavigate]
|
|
4856
5235
|
);
|
|
4857
|
-
return {
|
|
4858
|
-
pathname,
|
|
4859
|
-
query,
|
|
4860
|
-
isReady,
|
|
4861
|
-
activeLineId,
|
|
4862
|
-
activeWorkspaceId,
|
|
4863
|
-
isActive,
|
|
4864
|
-
isInSection,
|
|
4865
|
-
isLineView,
|
|
4866
|
-
isWorkspaceView,
|
|
4867
|
-
goToDashboard,
|
|
4868
|
-
goToWorkspace,
|
|
4869
|
-
goToLine,
|
|
4870
|
-
goToTargets,
|
|
4871
|
-
goToShifts,
|
|
4872
|
-
goToLeaderboard,
|
|
4873
|
-
goToFactoryView,
|
|
4874
|
-
goToProfile,
|
|
4875
|
-
navigate
|
|
4876
|
-
};
|
|
4877
|
-
}
|
|
4878
|
-
function useWorkspaceNavigation() {
|
|
4879
|
-
const { defaultTimezone } = useDateTimeConfig();
|
|
4880
|
-
const getWorkspaceNavigationParams3 = useCallback(
|
|
4881
|
-
(workspaceId, options) => {
|
|
4882
|
-
let dateToUse = options?.date;
|
|
4883
|
-
if (!dateToUse && options?.useCurrentDate) {
|
|
4884
|
-
dateToUse = getOperationalDate(defaultTimezone || "UTC");
|
|
4885
|
-
}
|
|
4886
|
-
return {
|
|
4887
|
-
workspaceId,
|
|
4888
|
-
date: dateToUse,
|
|
4889
|
-
shift: options?.shift,
|
|
4890
|
-
sourceType: options?.sourceType
|
|
4891
|
-
};
|
|
4892
|
-
},
|
|
4893
|
-
[defaultTimezone]
|
|
4894
|
-
);
|
|
4895
|
-
return {
|
|
4896
|
-
getWorkspaceNavigationParams: getWorkspaceNavigationParams3
|
|
4897
|
-
};
|
|
4898
|
-
}
|
|
4899
|
-
function useDateFormatter() {
|
|
4900
|
-
const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
|
|
4901
|
-
const formatDate = useCallback(
|
|
4902
|
-
(date, formatString) => {
|
|
4903
|
-
const dateObj = typeof date === "string" ? parseISO(date) : date;
|
|
4904
|
-
if (!isValid(dateObj)) return "Invalid Date";
|
|
4905
|
-
const tz = defaultTimezone || "UTC";
|
|
4906
|
-
if (formatString) {
|
|
4907
|
-
return formatInTimeZone(dateObj, tz, formatString);
|
|
4908
|
-
}
|
|
4909
|
-
const effectiveOptions = dateFormatOptions || { year: "numeric", month: "short", day: "numeric" };
|
|
4910
|
-
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
4911
|
-
},
|
|
4912
|
-
[defaultTimezone, defaultLocale, dateFormatOptions]
|
|
4913
|
-
);
|
|
4914
|
-
const formatTime2 = useCallback(
|
|
4915
|
-
(date, formatString) => {
|
|
4916
|
-
const dateObj = typeof date === "string" ? parseISO(date) : date;
|
|
4917
|
-
if (!isValid(dateObj)) return "Invalid Time";
|
|
4918
|
-
const tz = defaultTimezone || "UTC";
|
|
4919
|
-
if (formatString) {
|
|
4920
|
-
return formatInTimeZone(dateObj, tz, formatString);
|
|
4921
|
-
}
|
|
4922
|
-
const effectiveOptions = timeFormatOptions || { hour: "numeric", minute: "numeric" };
|
|
4923
|
-
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
4924
|
-
},
|
|
4925
|
-
[defaultTimezone, defaultLocale, timeFormatOptions]
|
|
4926
|
-
);
|
|
4927
|
-
const formatDateTime = useCallback(
|
|
4928
|
-
(date, formatString) => {
|
|
4929
|
-
const dateObj = typeof date === "string" ? parseISO(date) : date;
|
|
4930
|
-
if (!isValid(dateObj)) return "Invalid Date/Time";
|
|
4931
|
-
const tz = defaultTimezone || "UTC";
|
|
4932
|
-
if (formatString) {
|
|
4933
|
-
return formatInTimeZone(dateObj, tz, formatString);
|
|
4934
|
-
}
|
|
4935
|
-
const effectiveOptions = dateTimeFormatOptions || { year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "numeric" };
|
|
4936
|
-
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
4937
|
-
},
|
|
4938
|
-
[defaultTimezone, defaultLocale, dateTimeFormatOptions]
|
|
4939
|
-
);
|
|
4940
|
-
const getNow = useCallback(() => {
|
|
4941
|
-
return /* @__PURE__ */ new Date();
|
|
4942
|
-
}, []);
|
|
4943
|
-
return {
|
|
4944
|
-
formatDate,
|
|
4945
|
-
formatTime: formatTime2,
|
|
4946
|
-
formatDateTime,
|
|
4947
|
-
getNow,
|
|
4948
|
-
timezone: defaultTimezone || "UTC",
|
|
4949
|
-
locale: defaultLocale || "en-US"
|
|
4950
|
-
};
|
|
4951
|
-
}
|
|
4952
|
-
var useFormatNumber = () => {
|
|
4953
|
-
const { defaultLocale } = useDateTimeConfig();
|
|
4954
|
-
const formatNumber = useCallback(
|
|
4955
|
-
(value, options) => {
|
|
4956
|
-
try {
|
|
4957
|
-
return new Intl.NumberFormat(defaultLocale || "en-US", options).format(value);
|
|
4958
|
-
} catch (error) {
|
|
4959
|
-
console.error("Error formatting number:", error);
|
|
4960
|
-
return String(value);
|
|
4961
|
-
}
|
|
4962
|
-
},
|
|
4963
|
-
[defaultLocale]
|
|
4964
|
-
);
|
|
4965
|
-
return { formatNumber };
|
|
4966
|
-
};
|
|
4967
|
-
|
|
4968
|
-
// src/lib/utils/dashboardReload.ts
|
|
4969
|
-
var createThrottledReload = (interval = 5e3) => {
|
|
4970
|
-
let last = 0;
|
|
4971
|
-
let queued = false;
|
|
4972
|
-
const doReload = () => {
|
|
4973
|
-
if (typeof window !== "undefined") {
|
|
4974
|
-
window.location.reload();
|
|
4975
|
-
}
|
|
4976
|
-
};
|
|
4977
|
-
return () => {
|
|
4978
|
-
const now2 = Date.now();
|
|
4979
|
-
if (now2 - last >= interval) {
|
|
4980
|
-
last = now2;
|
|
4981
|
-
doReload();
|
|
4982
|
-
} else if (!queued) {
|
|
4983
|
-
queued = true;
|
|
4984
|
-
setTimeout(() => {
|
|
4985
|
-
queued = false;
|
|
4986
|
-
last = Date.now();
|
|
4987
|
-
doReload();
|
|
4988
|
-
}, interval - (now2 - last));
|
|
4989
|
-
}
|
|
4990
|
-
};
|
|
4991
|
-
};
|
|
4992
|
-
var throttledReloadDashboard = createThrottledReload(5e3);
|
|
4993
|
-
|
|
4994
|
-
// src/lib/hooks/useHlsStream.ts
|
|
4995
|
-
var HLS_CONFIG = {
|
|
4996
|
-
maxBufferLength: 8,
|
|
4997
|
-
maxMaxBufferLength: 15,
|
|
4998
|
-
lowLatencyMode: false,
|
|
4999
|
-
enableWorker: true,
|
|
5000
|
-
// Retry + timeout tuned for quick recovery
|
|
5001
|
-
manifestLoadingMaxRetry: 4,
|
|
5002
|
-
levelLoadingMaxRetry: 3,
|
|
5003
|
-
fragLoadingMaxRetry: 4,
|
|
5004
|
-
manifestLoadingRetryDelay: 500,
|
|
5005
|
-
levelLoadingRetryDelay: 500,
|
|
5006
|
-
fragLoadingRetryDelay: 500,
|
|
5007
|
-
manifestLoadingTimeOut: 1e4,
|
|
5008
|
-
levelLoadingTimeOut: 8e3,
|
|
5009
|
-
fragLoadingTimeOut: 1e4,
|
|
5010
|
-
liveSyncDurationCount: 2
|
|
5011
|
-
// Follow live edge aggressively
|
|
5012
|
-
};
|
|
5013
|
-
function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
|
|
5014
|
-
const [restartKey, setRestartKey] = useState(0);
|
|
5015
|
-
const hlsRef = useRef(null);
|
|
5016
|
-
const stallCheckIntervalRef = useRef(null);
|
|
5017
|
-
const noProgressTimerRef = useRef(null);
|
|
5018
|
-
const lastTimeUpdateRef = useRef(0);
|
|
5019
|
-
const softRestartCountRef = useRef(0);
|
|
5020
|
-
const isNativeHlsRef = useRef(false);
|
|
5021
|
-
const waitingTimerRef = useRef(null);
|
|
5022
|
-
const cleanup = () => {
|
|
5023
|
-
if (stallCheckIntervalRef.current) {
|
|
5024
|
-
clearInterval(stallCheckIntervalRef.current);
|
|
5025
|
-
stallCheckIntervalRef.current = null;
|
|
5026
|
-
}
|
|
5027
|
-
if (noProgressTimerRef.current) {
|
|
5028
|
-
clearTimeout(noProgressTimerRef.current);
|
|
5029
|
-
noProgressTimerRef.current = null;
|
|
5030
|
-
}
|
|
5031
|
-
if (waitingTimerRef.current) {
|
|
5032
|
-
clearTimeout(waitingTimerRef.current);
|
|
5033
|
-
waitingTimerRef.current = null;
|
|
5034
|
-
}
|
|
5035
|
-
if (hlsRef.current) {
|
|
5036
|
-
hlsRef.current.destroy();
|
|
5037
|
-
hlsRef.current = null;
|
|
5038
|
-
}
|
|
5039
|
-
const video = videoRef.current;
|
|
5040
|
-
if (video) {
|
|
5041
|
-
video.pause();
|
|
5042
|
-
video.removeAttribute("src");
|
|
5043
|
-
video.load();
|
|
5044
|
-
video.removeEventListener("waiting", handleWaiting);
|
|
5045
|
-
video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
5046
|
-
video.removeEventListener("error", handleNativeError);
|
|
5047
|
-
}
|
|
5048
|
-
lastTimeUpdateRef.current = 0;
|
|
5049
|
-
softRestartCountRef.current = 0;
|
|
5050
|
-
};
|
|
5051
|
-
const seekToLiveEdge = () => {
|
|
5052
|
-
const hls = hlsRef.current;
|
|
5053
|
-
const video = videoRef.current;
|
|
5054
|
-
if (!hls || !video) return;
|
|
5055
|
-
if (hls.liveSyncPosition !== null && hls.liveSyncPosition !== void 0) {
|
|
5056
|
-
video.currentTime = hls.liveSyncPosition;
|
|
5057
|
-
} else if (hls.levels?.[hls.currentLevel]?.details) {
|
|
5058
|
-
const levelDetails = hls.levels[hls.currentLevel].details;
|
|
5059
|
-
if (levelDetails && levelDetails.edge !== void 0) {
|
|
5060
|
-
video.currentTime = Math.max(0, levelDetails.edge - 5);
|
|
5061
|
-
}
|
|
5062
|
-
}
|
|
5236
|
+
return {
|
|
5237
|
+
pathname,
|
|
5238
|
+
query,
|
|
5239
|
+
isReady,
|
|
5240
|
+
activeLineId,
|
|
5241
|
+
activeWorkspaceId,
|
|
5242
|
+
isActive,
|
|
5243
|
+
isInSection,
|
|
5244
|
+
isLineView,
|
|
5245
|
+
isWorkspaceView,
|
|
5246
|
+
goToDashboard,
|
|
5247
|
+
goToWorkspace,
|
|
5248
|
+
goToLine,
|
|
5249
|
+
goToTargets,
|
|
5250
|
+
goToShifts,
|
|
5251
|
+
goToLeaderboard,
|
|
5252
|
+
goToFactoryView,
|
|
5253
|
+
goToProfile,
|
|
5254
|
+
navigate
|
|
5063
5255
|
};
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
softRestartCountRef.current++;
|
|
5073
|
-
if (softRestartCountRef.current >= 5) {
|
|
5074
|
-
hardRestart(`${reason} (escalated after ${softRestartCountRef.current} soft restarts)`);
|
|
5256
|
+
}
|
|
5257
|
+
function useWorkspaceNavigation() {
|
|
5258
|
+
const { defaultTimezone } = useDateTimeConfig();
|
|
5259
|
+
const getWorkspaceNavigationParams3 = useCallback(
|
|
5260
|
+
(workspaceId, options) => {
|
|
5261
|
+
let dateToUse = options?.date;
|
|
5262
|
+
if (!dateToUse && options?.useCurrentDate) {
|
|
5263
|
+
dateToUse = getOperationalDate(defaultTimezone || "UTC");
|
|
5075
5264
|
}
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5265
|
+
return {
|
|
5266
|
+
workspaceId,
|
|
5267
|
+
date: dateToUse,
|
|
5268
|
+
shift: options?.shift,
|
|
5269
|
+
sourceType: options?.sourceType
|
|
5270
|
+
};
|
|
5271
|
+
},
|
|
5272
|
+
[defaultTimezone]
|
|
5273
|
+
);
|
|
5274
|
+
return {
|
|
5275
|
+
getWorkspaceNavigationParams: getWorkspaceNavigationParams3
|
|
5080
5276
|
};
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
if (
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5277
|
+
}
|
|
5278
|
+
function useDateFormatter() {
|
|
5279
|
+
const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
|
|
5280
|
+
const formatDate = useCallback(
|
|
5281
|
+
(date, formatString) => {
|
|
5282
|
+
const dateObj = typeof date === "string" ? parseISO(date) : date;
|
|
5283
|
+
if (!isValid(dateObj)) return "Invalid Date";
|
|
5284
|
+
const tz = defaultTimezone || "UTC";
|
|
5285
|
+
if (formatString) {
|
|
5286
|
+
return formatInTimeZone(dateObj, tz, formatString);
|
|
5091
5287
|
}
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
const
|
|
5102
|
-
if (
|
|
5103
|
-
|
|
5288
|
+
const effectiveOptions = dateFormatOptions || { year: "numeric", month: "short", day: "numeric" };
|
|
5289
|
+
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
5290
|
+
},
|
|
5291
|
+
[defaultTimezone, defaultLocale, dateFormatOptions]
|
|
5292
|
+
);
|
|
5293
|
+
const formatTime2 = useCallback(
|
|
5294
|
+
(date, formatString) => {
|
|
5295
|
+
const dateObj = typeof date === "string" ? parseISO(date) : date;
|
|
5296
|
+
if (!isValid(dateObj)) return "Invalid Time";
|
|
5297
|
+
const tz = defaultTimezone || "UTC";
|
|
5298
|
+
if (formatString) {
|
|
5299
|
+
return formatInTimeZone(dateObj, tz, formatString);
|
|
5104
5300
|
}
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
console.error("[HLS] Native video error");
|
|
5118
|
-
hardRestart("native video error");
|
|
5119
|
-
};
|
|
5120
|
-
const startStallDetection = () => {
|
|
5121
|
-
if (isNativeHlsRef.current) return;
|
|
5122
|
-
stallCheckIntervalRef.current = setInterval(() => {
|
|
5123
|
-
const video = videoRef.current;
|
|
5124
|
-
if (!video || video.paused || video.ended) return;
|
|
5125
|
-
const currentTime = video.currentTime;
|
|
5126
|
-
const lastTime = lastTimeUpdateRef.current;
|
|
5127
|
-
if (Math.abs(currentTime - lastTime) < 0.1 && video.readyState >= 2) {
|
|
5128
|
-
console.warn("[HLS] Playback stall detected");
|
|
5129
|
-
if (!noProgressTimerRef.current) {
|
|
5130
|
-
noProgressTimerRef.current = setTimeout(() => {
|
|
5131
|
-
softRestart("playback stall");
|
|
5132
|
-
noProgressTimerRef.current = null;
|
|
5133
|
-
}, 8e3);
|
|
5134
|
-
}
|
|
5135
|
-
} else {
|
|
5136
|
-
if (noProgressTimerRef.current) {
|
|
5137
|
-
clearTimeout(noProgressTimerRef.current);
|
|
5138
|
-
noProgressTimerRef.current = null;
|
|
5139
|
-
}
|
|
5301
|
+
const effectiveOptions = timeFormatOptions || { hour: "numeric", minute: "numeric" };
|
|
5302
|
+
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
5303
|
+
},
|
|
5304
|
+
[defaultTimezone, defaultLocale, timeFormatOptions]
|
|
5305
|
+
);
|
|
5306
|
+
const formatDateTime = useCallback(
|
|
5307
|
+
(date, formatString) => {
|
|
5308
|
+
const dateObj = typeof date === "string" ? parseISO(date) : date;
|
|
5309
|
+
if (!isValid(dateObj)) return "Invalid Date/Time";
|
|
5310
|
+
const tz = defaultTimezone || "UTC";
|
|
5311
|
+
if (formatString) {
|
|
5312
|
+
return formatInTimeZone(dateObj, tz, formatString);
|
|
5140
5313
|
}
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
if (!video) return;
|
|
5150
|
-
isNativeHlsRef.current = video.canPlayType("application/vnd.apple.mpegurl") === "probably";
|
|
5151
|
-
if (Hls2.isSupported() && !isNativeHlsRef.current) {
|
|
5152
|
-
const hls = new Hls2(HLS_CONFIG);
|
|
5153
|
-
hlsRef.current = hls;
|
|
5154
|
-
hls.attachMedia(video);
|
|
5155
|
-
hls.loadSource(src);
|
|
5156
|
-
hls.on(Hls2.Events.ERROR, (_, data) => {
|
|
5157
|
-
if (!data.fatal) return;
|
|
5158
|
-
console.error("[HLS] Fatal error:", data.type, data.details);
|
|
5159
|
-
if (data.response?.code === 404) {
|
|
5160
|
-
hardRestart("404 hard restart");
|
|
5161
|
-
return;
|
|
5162
|
-
}
|
|
5163
|
-
switch (data.type) {
|
|
5164
|
-
case Hls2.ErrorTypes.NETWORK_ERROR:
|
|
5165
|
-
case Hls2.ErrorTypes.MEDIA_ERROR:
|
|
5166
|
-
softRestart(`${data.type}: ${data.details}`);
|
|
5167
|
-
break;
|
|
5168
|
-
default:
|
|
5169
|
-
hardRestart(`Fatal ${data.type}: ${data.details}`);
|
|
5170
|
-
break;
|
|
5171
|
-
}
|
|
5172
|
-
});
|
|
5173
|
-
hls.on(Hls2.Events.MANIFEST_PARSED, () => {
|
|
5174
|
-
video.play().catch((err) => {
|
|
5175
|
-
console.error("[HLS] Play failed:", err);
|
|
5176
|
-
});
|
|
5177
|
-
});
|
|
5178
|
-
video.addEventListener("waiting", handleWaiting);
|
|
5179
|
-
video.addEventListener("timeupdate", handleTimeUpdate);
|
|
5180
|
-
startStallDetection();
|
|
5181
|
-
} else if (isNativeHlsRef.current) {
|
|
5182
|
-
console.log("[HLS] Using native HLS");
|
|
5183
|
-
video.src = src;
|
|
5184
|
-
video.addEventListener("error", handleNativeError);
|
|
5185
|
-
video.play().catch((err) => {
|
|
5186
|
-
console.error("[HLS] Native play failed:", err);
|
|
5187
|
-
});
|
|
5188
|
-
} else {
|
|
5189
|
-
console.error("[HLS] HLS not supported");
|
|
5190
|
-
}
|
|
5191
|
-
return cleanup;
|
|
5192
|
-
}, [src, shouldPlay, restartKey, onFatalError]);
|
|
5314
|
+
const effectiveOptions = dateTimeFormatOptions || { year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "numeric" };
|
|
5315
|
+
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
5316
|
+
},
|
|
5317
|
+
[defaultTimezone, defaultLocale, dateTimeFormatOptions]
|
|
5318
|
+
);
|
|
5319
|
+
const getNow = useCallback(() => {
|
|
5320
|
+
return /* @__PURE__ */ new Date();
|
|
5321
|
+
}, []);
|
|
5193
5322
|
return {
|
|
5194
|
-
|
|
5195
|
-
|
|
5323
|
+
formatDate,
|
|
5324
|
+
formatTime: formatTime2,
|
|
5325
|
+
formatDateTime,
|
|
5326
|
+
getNow,
|
|
5327
|
+
timezone: defaultTimezone || "UTC",
|
|
5328
|
+
locale: defaultLocale || "en-US"
|
|
5196
5329
|
};
|
|
5197
5330
|
}
|
|
5331
|
+
var useFormatNumber = () => {
|
|
5332
|
+
const { defaultLocale } = useDateTimeConfig();
|
|
5333
|
+
const formatNumber = useCallback(
|
|
5334
|
+
(value, options) => {
|
|
5335
|
+
try {
|
|
5336
|
+
return new Intl.NumberFormat(defaultLocale || "en-US", options).format(value);
|
|
5337
|
+
} catch (error) {
|
|
5338
|
+
console.error("Error formatting number:", error);
|
|
5339
|
+
return String(value);
|
|
5340
|
+
}
|
|
5341
|
+
},
|
|
5342
|
+
[defaultLocale]
|
|
5343
|
+
);
|
|
5344
|
+
return { formatNumber };
|
|
5345
|
+
};
|
|
5198
5346
|
|
|
5199
5347
|
// src/lib/utils/api.ts
|
|
5200
5348
|
var apiUtils = {
|
|
@@ -17136,15 +17284,15 @@ var HourlyOutputChart = ({
|
|
|
17136
17284
|
renderLegend()
|
|
17137
17285
|
] });
|
|
17138
17286
|
};
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
}
|
|
17147
|
-
|
|
17287
|
+
function getTrendArrowAndColor(trend) {
|
|
17288
|
+
if (trend > 0) {
|
|
17289
|
+
return { arrow: "\u2191", color: "text-green-400" };
|
|
17290
|
+
} else if (trend < 0) {
|
|
17291
|
+
return { arrow: "\u2193", color: "text-red-400" };
|
|
17292
|
+
} else {
|
|
17293
|
+
return { arrow: "\u2192", color: "text-gray-400" };
|
|
17294
|
+
}
|
|
17295
|
+
}
|
|
17148
17296
|
var VideoCard = React14__default.memo(({
|
|
17149
17297
|
workspace,
|
|
17150
17298
|
hlsUrl,
|
|
@@ -17152,14 +17300,29 @@ var VideoCard = React14__default.memo(({
|
|
|
17152
17300
|
onClick,
|
|
17153
17301
|
onFatalError,
|
|
17154
17302
|
isVeryLowEfficiency = false,
|
|
17303
|
+
cropping,
|
|
17304
|
+
canvasFps = 30,
|
|
17305
|
+
useRAF = true,
|
|
17155
17306
|
className = ""
|
|
17156
17307
|
}) => {
|
|
17157
17308
|
const videoRef = useRef(null);
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
17309
|
+
const canvasRef = useRef(null);
|
|
17310
|
+
if (cropping) {
|
|
17311
|
+
useHlsStreamWithCropping(videoRef, canvasRef, {
|
|
17312
|
+
src: hlsUrl,
|
|
17313
|
+
shouldPlay,
|
|
17314
|
+
cropping,
|
|
17315
|
+
canvasFps,
|
|
17316
|
+
useRAF,
|
|
17317
|
+
onFatalError: onFatalError ?? (() => throttledReloadDashboard())
|
|
17318
|
+
});
|
|
17319
|
+
} else {
|
|
17320
|
+
useHlsStream(videoRef, {
|
|
17321
|
+
src: hlsUrl,
|
|
17322
|
+
shouldPlay,
|
|
17323
|
+
onFatalError: onFatalError ?? (() => throttledReloadDashboard())
|
|
17324
|
+
});
|
|
17325
|
+
}
|
|
17163
17326
|
const displayName = getWorkspaceDisplayName(workspace.workspace_name);
|
|
17164
17327
|
workspace.workspace_uuid || workspace.workspace_name;
|
|
17165
17328
|
const getEfficiencyOverlayColor = (efficiency) => {
|
|
@@ -17214,17 +17377,26 @@ var VideoCard = React14__default.memo(({
|
|
|
17214
17377
|
/* @__PURE__ */ jsx(Camera, { className: "w-6 h-6 text-gray-500" }),
|
|
17215
17378
|
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500 mt-1", children: "Loading..." })
|
|
17216
17379
|
] }) }),
|
|
17217
|
-
/* @__PURE__ */
|
|
17218
|
-
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17224
|
-
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
17380
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-10", children: [
|
|
17381
|
+
/* @__PURE__ */ jsx(
|
|
17382
|
+
"video",
|
|
17383
|
+
{
|
|
17384
|
+
ref: videoRef,
|
|
17385
|
+
className: `h-full w-full object-cover ${cropping ? "hidden" : ""}`,
|
|
17386
|
+
playsInline: true,
|
|
17387
|
+
muted: true,
|
|
17388
|
+
disablePictureInPicture: true,
|
|
17389
|
+
controlsList: "nodownload noplaybackrate"
|
|
17390
|
+
}
|
|
17391
|
+
),
|
|
17392
|
+
cropping && /* @__PURE__ */ jsx(
|
|
17393
|
+
"canvas",
|
|
17394
|
+
{
|
|
17395
|
+
ref: canvasRef,
|
|
17396
|
+
className: "h-full w-full object-cover"
|
|
17397
|
+
}
|
|
17398
|
+
)
|
|
17399
|
+
] }),
|
|
17228
17400
|
/* @__PURE__ */ jsx("div", { className: `absolute inset-0 z-20 pointer-events-none ${efficiencyOverlayClass}` }),
|
|
17229
17401
|
/* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 z-30 bg-black/70 backdrop-blur-sm rounded px-2 py-0.5 text-white text-xs font-semibold border border-white/10", children: [
|
|
17230
17402
|
Math.round(workspace.efficiency),
|
|
@@ -17260,7 +17432,7 @@ var VideoCard = React14__default.memo(({
|
|
|
17260
17432
|
}
|
|
17261
17433
|
);
|
|
17262
17434
|
}, (prevProps, nextProps) => {
|
|
17263
|
-
return prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.workspace_name === nextProps.workspace.workspace_name && Math.abs(prevProps.workspace.efficiency - nextProps.workspace.efficiency) < 1 && prevProps.hlsUrl === nextProps.hlsUrl && prevProps.shouldPlay === nextProps.shouldPlay;
|
|
17435
|
+
return prevProps.workspace.workspace_uuid === nextProps.workspace.workspace_uuid && prevProps.workspace.workspace_name === nextProps.workspace.workspace_name && Math.abs(prevProps.workspace.efficiency - nextProps.workspace.efficiency) < 1 && prevProps.hlsUrl === nextProps.hlsUrl && prevProps.shouldPlay === nextProps.shouldPlay && prevProps.cropping?.x === nextProps.cropping?.x && prevProps.cropping?.y === nextProps.cropping?.y && prevProps.cropping?.width === nextProps.cropping?.width && prevProps.cropping?.height === nextProps.cropping?.height;
|
|
17264
17436
|
});
|
|
17265
17437
|
VideoCard.displayName = "VideoCard";
|
|
17266
17438
|
var DEFAULT_WORKSPACE_HLS_URLS = {
|
|
@@ -17288,6 +17460,8 @@ var VideoGridView = React14__default.memo(({
|
|
|
17288
17460
|
const observerRef = useRef(null);
|
|
17289
17461
|
const [gridCols, setGridCols] = useState(4);
|
|
17290
17462
|
const [visibleWorkspaces, setVisibleWorkspaces] = useState(/* @__PURE__ */ new Set());
|
|
17463
|
+
const videoConfig = useVideoConfig();
|
|
17464
|
+
const { cropping, canvasConfig } = videoConfig;
|
|
17291
17465
|
const mergedVideoSources = {
|
|
17292
17466
|
defaultHlsUrl: videoSources.defaultHlsUrl || DEFAULT_HLS_URL,
|
|
17293
17467
|
workspaceHlsUrls: { ...DEFAULT_WORKSPACE_HLS_URLS, ...videoSources.workspaceHlsUrls }
|
|
@@ -17296,6 +17470,13 @@ var VideoGridView = React14__default.memo(({
|
|
|
17296
17470
|
const wsName = workspaceName.toUpperCase();
|
|
17297
17471
|
return mergedVideoSources.workspaceHlsUrls[wsName] || mergedVideoSources.defaultHlsUrl;
|
|
17298
17472
|
}, [mergedVideoSources]);
|
|
17473
|
+
const getWorkspaceCropping = useCallback((workspaceName) => {
|
|
17474
|
+
if (!cropping) return void 0;
|
|
17475
|
+
if (cropping.workspaceOverrides?.[workspaceName]) {
|
|
17476
|
+
return cropping.workspaceOverrides[workspaceName];
|
|
17477
|
+
}
|
|
17478
|
+
return cropping.default;
|
|
17479
|
+
}, [cropping]);
|
|
17299
17480
|
const veryLowEfficiencyWorkspaces = useMemo(() => {
|
|
17300
17481
|
return new Set(
|
|
17301
17482
|
workspaces.filter((w) => w.efficiency < 50 && w.efficiency >= 10).map((w) => w.workspace_name)
|
|
@@ -17434,6 +17615,7 @@ var VideoGridView = React14__default.memo(({
|
|
|
17434
17615
|
const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
|
|
17435
17616
|
const isVisible = visibleWorkspaces.has(workspaceId);
|
|
17436
17617
|
const isVeryLowEfficiency = veryLowEfficiencyWorkspaces.has(workspace.workspace_name);
|
|
17618
|
+
const workspaceCropping = getWorkspaceCropping(workspace.workspace_name);
|
|
17437
17619
|
return /* @__PURE__ */ jsx(
|
|
17438
17620
|
"div",
|
|
17439
17621
|
{
|
|
@@ -17448,7 +17630,10 @@ var VideoGridView = React14__default.memo(({
|
|
|
17448
17630
|
shouldPlay: isVisible,
|
|
17449
17631
|
onClick: () => handleWorkspaceClick(workspace),
|
|
17450
17632
|
onFatalError: throttledReloadDashboard,
|
|
17451
|
-
isVeryLowEfficiency
|
|
17633
|
+
isVeryLowEfficiency,
|
|
17634
|
+
cropping: workspaceCropping,
|
|
17635
|
+
canvasFps: canvasConfig?.fps,
|
|
17636
|
+
useRAF: canvasConfig?.useRAF
|
|
17452
17637
|
}
|
|
17453
17638
|
)
|
|
17454
17639
|
},
|
|
@@ -19634,7 +19819,8 @@ var WorkspaceHistoryCalendar = ({
|
|
|
19634
19819
|
pph: 0,
|
|
19635
19820
|
pphThreshold: 0,
|
|
19636
19821
|
idealOutput: 0,
|
|
19637
|
-
rank: 0
|
|
19822
|
+
rank: 0,
|
|
19823
|
+
idleTime: 0
|
|
19638
19824
|
},
|
|
19639
19825
|
nightShift: {
|
|
19640
19826
|
efficiency: 0,
|
|
@@ -19643,7 +19829,8 @@ var WorkspaceHistoryCalendar = ({
|
|
|
19643
19829
|
pph: 0,
|
|
19644
19830
|
pphThreshold: 0,
|
|
19645
19831
|
idealOutput: 0,
|
|
19646
|
-
rank: 0
|
|
19832
|
+
rank: 0,
|
|
19833
|
+
idleTime: 0
|
|
19647
19834
|
}
|
|
19648
19835
|
});
|
|
19649
19836
|
}
|
|
@@ -19672,7 +19859,8 @@ var WorkspaceHistoryCalendar = ({
|
|
|
19672
19859
|
avgEfficiency: Math.round(validShifts.reduce((sum, shift) => sum + shift.efficiency, 0) / validShifts.length),
|
|
19673
19860
|
avgCycleTime: Math.round(validShifts.reduce((sum, shift) => sum + shift.cycleTime, 0) / validShifts.length),
|
|
19674
19861
|
badDaysCount: badShiftsCount,
|
|
19675
|
-
totalDays: validShifts.length
|
|
19862
|
+
totalDays: validShifts.length,
|
|
19863
|
+
avgIdleTime: Math.round(validShifts.reduce((sum, shift) => sum + (shift.idleTime || 0), 0) / validShifts.length)
|
|
19676
19864
|
};
|
|
19677
19865
|
}, [data, month, year, configuredTimezone]);
|
|
19678
19866
|
const handleDayClick = useCallback((day, shift) => {
|
|
@@ -19843,6 +20031,10 @@ var WorkspaceHistoryCalendar = ({
|
|
|
19843
20031
|
monthlyMetrics.avgCycleTime,
|
|
19844
20032
|
"s"
|
|
19845
20033
|
] })
|
|
20034
|
+
] }),
|
|
20035
|
+
/* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
|
|
20036
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-gray-600 mb-1", children: "Avg Idle Time" }),
|
|
20037
|
+
/* @__PURE__ */ jsx("div", { className: "text-xl font-semibold text-gray-900", children: formatIdleTime(monthlyMetrics.avgIdleTime) })
|
|
19846
20038
|
] })
|
|
19847
20039
|
] }) : /* @__PURE__ */ jsxs("div", { className: "text-center py-8 text-gray-500 bg-gray-50 rounded-lg border border-gray-200", children: [
|
|
19848
20040
|
/* @__PURE__ */ jsx("svg", { className: "w-12 h-12 text-gray-400 mx-auto mb-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" }) }),
|
|
@@ -22268,12 +22460,12 @@ var getEfficiencyColor = (efficiency) => {
|
|
|
22268
22460
|
return "bg-[#E34329]/90 hover:bg-[#E34329]/95";
|
|
22269
22461
|
}
|
|
22270
22462
|
};
|
|
22271
|
-
var
|
|
22463
|
+
var TREND_STYLES = {
|
|
22272
22464
|
0: { arrow: "\u2193", color: "text-red-500 font-bold text-shadow" },
|
|
22273
22465
|
1: { arrow: "=", color: "text-gray-500 font-bold text-shadow" },
|
|
22274
22466
|
2: { arrow: "\u2191", color: "text-green-500 font-bold text-shadow" }
|
|
22275
22467
|
};
|
|
22276
|
-
var getTrendArrowAndColor2 = (trend) =>
|
|
22468
|
+
var getTrendArrowAndColor2 = (trend) => TREND_STYLES[trend] || { arrow: "", color: "" };
|
|
22277
22469
|
var ARROW_POSITIONS = {
|
|
22278
22470
|
top: "-bottom-6",
|
|
22279
22471
|
"middle-top": "-bottom-6",
|
|
@@ -29960,8 +30152,8 @@ var WorkspaceDetailView = ({
|
|
|
29960
30152
|
if (!dayEntry) {
|
|
29961
30153
|
dayEntry = {
|
|
29962
30154
|
date: dateObj,
|
|
29963
|
-
dayShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0 },
|
|
29964
|
-
nightShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0 }
|
|
30155
|
+
dayShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0, idleTime: 0 },
|
|
30156
|
+
nightShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0, idleTime: 0 }
|
|
29965
30157
|
};
|
|
29966
30158
|
dayDataMap.set(dateKey, dayEntry);
|
|
29967
30159
|
}
|
|
@@ -29973,6 +30165,7 @@ var WorkspaceDetailView = ({
|
|
|
29973
30165
|
shiftTarget.pphThreshold = metric.pph_threshold || 0;
|
|
29974
30166
|
shiftTarget.idealOutput = metric.ideal_output || 0;
|
|
29975
30167
|
shiftTarget.rank = metric.workspace_rank || 0;
|
|
30168
|
+
shiftTarget.idleTime = metric.idle_time || 0;
|
|
29976
30169
|
});
|
|
29977
30170
|
const processedData = Array.from(dayDataMap.values());
|
|
29978
30171
|
console.log(`[handleMonthlyDataLoaded] Transformed data for calendar:`, {
|
|
@@ -30713,4 +30906,4 @@ var S3Service = class {
|
|
|
30713
30906
|
}
|
|
30714
30907
|
};
|
|
30715
30908
|
|
|
30716
|
-
export { ACTION_NAMES, AIAgentView_default as AIAgentView, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedTargetsView, BarChart, BaseHistoryCalendar, BottlenecksContent, BreakNotificationPopup, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_THEME_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, EmptyStateMessage, FactoryView_default as FactoryView, GridComponentsPlaceholder, Header, HelpView_default as HelpView, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend5 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LiveTimer, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSpinner_default as LoadingSpinner, LoginPage, LoginView_default as LoginView, MainLayout, MetricCard_default as MetricCard, NoWorkspaceData, OptifyeAgentClient, OutputProgressChart, PageHeader, ProfileView_default as ProfileView, RegistryProvider, S3Service, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SingleVideoStream_default as SingleVideoStream, Skeleton, SlackAPI, SupabaseProvider, TargetWorkspaceGrid, TargetsView_default as TargetsView, ThreadSidebar, TimeDisplay_default as TimeDisplay, TimePickerDropdown, VideoCard, VideoGridView, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceGrid, WorkspaceGridItem, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createStreamProxyHandler, createSupabaseClient, createThrottledReload, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatTimeInZone, fromUrlFriendlyName, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultTabForWorkspace, getManufacturingInsights, getMetricsTablePrefix, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isTransitionPeriod, isValidLineInfoPayload, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, mergeWithDefaultConfig, optifyeAgentClient, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, s3VideoPreloader, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useActiveBreaks, useAnalyticsConfig, useAuth, useAuthConfig, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHistoricWorkspaceMetrics, useHlsStream, useHookOverride, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, useRealtimeLineMetrics, useRegistry, useShiftConfig, useShifts, useSupabase, useSupabaseClient, useTargets, useTheme, useThemeConfig, useThreads, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, videoPreloader, whatsappService, withAuth, withRegistry, workspaceService };
|
|
30909
|
+
export { ACTION_NAMES, AIAgentView_default as AIAgentView, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedTargetsView, BarChart, BaseHistoryCalendar, BottlenecksContent, BreakNotificationPopup, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, EmptyStateMessage, FactoryView_default as FactoryView, GridComponentsPlaceholder, Header, HelpView_default as HelpView, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend5 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LiveTimer, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSpinner_default as LoadingSpinner, LoginPage, LoginView_default as LoginView, MainLayout, MetricCard_default as MetricCard, NoWorkspaceData, OptifyeAgentClient, OutputProgressChart, PageHeader, ProfileView_default as ProfileView, RegistryProvider, S3Service, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SingleVideoStream_default as SingleVideoStream, Skeleton, SlackAPI, SupabaseProvider, TargetWorkspaceGrid, TargetsView_default as TargetsView, ThreadSidebar, TimeDisplay_default as TimeDisplay, TimePickerDropdown, VideoCard, VideoGridView, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceGrid, WorkspaceGridItem, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createStreamProxyHandler, createSupabaseClient, createThrottledReload, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatTimeInZone, fromUrlFriendlyName, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultTabForWorkspace, getManufacturingInsights, getMetricsTablePrefix, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isTransitionPeriod, isValidLineInfoPayload, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, mergeWithDefaultConfig, optifyeAgentClient, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, s3VideoPreloader, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useActiveBreaks, useAnalyticsConfig, useAuth, useAuthConfig, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, useRealtimeLineMetrics, useRegistry, useShiftConfig, useShifts, useSupabase, useSupabaseClient, useTargets, useTheme, useThemeConfig, useThreads, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, videoPreloader, whatsappService, withAuth, withRegistry, workspaceService };
|