@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.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
var React14 = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
var router = require('next/router');
|
|
6
|
-
var dateFns = require('date-fns');
|
|
7
6
|
var dateFnsTz = require('date-fns-tz');
|
|
8
|
-
var
|
|
7
|
+
var dateFns = require('date-fns');
|
|
9
8
|
var mixpanel = require('mixpanel-browser');
|
|
10
|
-
var
|
|
9
|
+
var supabaseJs = require('@supabase/supabase-js');
|
|
11
10
|
var Hls2 = require('hls.js');
|
|
11
|
+
var useSWR = require('swr');
|
|
12
12
|
var motionUtils = require('motion-utils');
|
|
13
13
|
var motionDom = require('motion-dom');
|
|
14
14
|
var recharts = require('recharts');
|
|
@@ -46,8 +46,8 @@ function _interopNamespace(e) {
|
|
|
46
46
|
|
|
47
47
|
var React14__namespace = /*#__PURE__*/_interopNamespace(React14);
|
|
48
48
|
var mixpanel__default = /*#__PURE__*/_interopDefault(mixpanel);
|
|
49
|
-
var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
|
|
50
49
|
var Hls2__default = /*#__PURE__*/_interopDefault(Hls2);
|
|
50
|
+
var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
|
|
51
51
|
var html2canvas__default = /*#__PURE__*/_interopDefault(html2canvas);
|
|
52
52
|
var jsPDF__default = /*#__PURE__*/_interopDefault(jsPDF);
|
|
53
53
|
var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive);
|
|
@@ -154,6 +154,12 @@ var DEFAULT_ANALYTICS_CONFIG = {
|
|
|
154
154
|
var DEFAULT_AUTH_CONFIG = {
|
|
155
155
|
// Defaults related to auth providers, redirects etc.
|
|
156
156
|
};
|
|
157
|
+
var DEFAULT_VIDEO_CONFIG = {
|
|
158
|
+
canvasConfig: {
|
|
159
|
+
fps: 30,
|
|
160
|
+
useRAF: true
|
|
161
|
+
}
|
|
162
|
+
};
|
|
157
163
|
var LINE_1_UUID = "910a224b-0abc-459a-babb-4c899824cfe7";
|
|
158
164
|
var DEFAULT_CONFIG = {
|
|
159
165
|
apiBaseUrl: void 0,
|
|
@@ -167,7 +173,8 @@ var DEFAULT_CONFIG = {
|
|
|
167
173
|
// Add entity config here
|
|
168
174
|
shiftConfig: DEFAULT_SHIFT_CONFIG,
|
|
169
175
|
workspaceConfig: DEFAULT_WORKSPACE_CONFIG,
|
|
170
|
-
endpoints: DEFAULT_ENDPOINTS_CONFIG
|
|
176
|
+
endpoints: DEFAULT_ENDPOINTS_CONFIG,
|
|
177
|
+
videoConfig: DEFAULT_VIDEO_CONFIG
|
|
171
178
|
};
|
|
172
179
|
|
|
173
180
|
// src/lib/utils/config.ts
|
|
@@ -299,172 +306,9 @@ function useCustomConfig() {
|
|
|
299
306
|
const { customConfig } = useDashboardConfig();
|
|
300
307
|
return customConfig ?? {};
|
|
301
308
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
loading: true,
|
|
306
|
-
error: null,
|
|
307
|
-
signOut: async () => {
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
var useAuth = () => React14.useContext(AuthContext);
|
|
311
|
-
var AuthProvider = ({ children }) => {
|
|
312
|
-
const supabase = useSupabase();
|
|
313
|
-
const { authConfig } = useDashboardConfig();
|
|
314
|
-
const [session, setSession] = React14.useState(null);
|
|
315
|
-
const [user, setUser] = React14.useState(null);
|
|
316
|
-
const [loading, setLoading] = React14.useState(true);
|
|
317
|
-
const [error, setError] = React14.useState(null);
|
|
318
|
-
const router$1 = router.useRouter();
|
|
319
|
-
const userProfileTable = authConfig?.userProfileTable;
|
|
320
|
-
const roleColumn = authConfig?.roleColumn || "role";
|
|
321
|
-
const fetchUserDetails = React14.useCallback(async (supabaseUser) => {
|
|
322
|
-
if (!supabaseUser) return null;
|
|
323
|
-
const basicUser = {
|
|
324
|
-
id: supabaseUser.id,
|
|
325
|
-
email: supabaseUser.email
|
|
326
|
-
};
|
|
327
|
-
if (!userProfileTable || !supabase) return basicUser;
|
|
328
|
-
try {
|
|
329
|
-
const timeoutPromise = new Promise(
|
|
330
|
-
(_, reject) => setTimeout(() => reject(new Error("Profile fetch timeout")), 5e3)
|
|
331
|
-
);
|
|
332
|
-
const fetchPromise = supabase.from(userProfileTable).select(roleColumn).eq("id", supabaseUser.id).single();
|
|
333
|
-
const { data: profile, error: profileError } = await Promise.race([
|
|
334
|
-
fetchPromise,
|
|
335
|
-
timeoutPromise
|
|
336
|
-
]);
|
|
337
|
-
if (profileError) {
|
|
338
|
-
if (profileError.message.includes("does not exist") || profileError.message.includes("No rows found") || profileError.code === "PGRST116") {
|
|
339
|
-
console.log("User profile table not found or user not in table, using basic auth info");
|
|
340
|
-
return basicUser;
|
|
341
|
-
}
|
|
342
|
-
console.error("Error fetching user profile:", profileError);
|
|
343
|
-
return basicUser;
|
|
344
|
-
}
|
|
345
|
-
const roleValue = profile ? profile[roleColumn] : void 0;
|
|
346
|
-
return { ...basicUser, role: roleValue };
|
|
347
|
-
} catch (err) {
|
|
348
|
-
console.error("Error fetching user profile:", err);
|
|
349
|
-
return basicUser;
|
|
350
|
-
}
|
|
351
|
-
}, [supabase, userProfileTable, roleColumn]);
|
|
352
|
-
React14.useEffect(() => {
|
|
353
|
-
if (!supabase) return;
|
|
354
|
-
let mounted = true;
|
|
355
|
-
const safetyTimeout = setTimeout(() => {
|
|
356
|
-
if (mounted) {
|
|
357
|
-
console.warn("Auth initialization taking too long, forcing loading to false");
|
|
358
|
-
setLoading(false);
|
|
359
|
-
}
|
|
360
|
-
}, 1e4);
|
|
361
|
-
const initializeAuth = async () => {
|
|
362
|
-
try {
|
|
363
|
-
const { data: { session: initialSession }, error: sessionError } = await supabase.auth.getSession();
|
|
364
|
-
if (!mounted) return;
|
|
365
|
-
if (sessionError) {
|
|
366
|
-
setError(sessionError);
|
|
367
|
-
setLoading(false);
|
|
368
|
-
clearTimeout(safetyTimeout);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
setSession(initialSession);
|
|
372
|
-
if (initialSession?.user) {
|
|
373
|
-
try {
|
|
374
|
-
const userDetails = await fetchUserDetails(initialSession.user);
|
|
375
|
-
if (mounted) setUser(userDetails);
|
|
376
|
-
} catch (err) {
|
|
377
|
-
console.error("Error fetching user details during init:", err);
|
|
378
|
-
if (mounted) {
|
|
379
|
-
setUser({
|
|
380
|
-
id: initialSession.user.id,
|
|
381
|
-
email: initialSession.user.email
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
} catch (err) {
|
|
387
|
-
if (mounted) setError(err instanceof Error ? err : new Error("Failed to initialize auth"));
|
|
388
|
-
} finally {
|
|
389
|
-
if (mounted) {
|
|
390
|
-
setLoading(false);
|
|
391
|
-
clearTimeout(safetyTimeout);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
initializeAuth();
|
|
396
|
-
const { data: { subscription } } = supabase.auth.onAuthStateChange(async (_event, currentSession) => {
|
|
397
|
-
if (!mounted) return;
|
|
398
|
-
setSession(currentSession);
|
|
399
|
-
setUser(null);
|
|
400
|
-
setLoading(true);
|
|
401
|
-
if (currentSession?.user) {
|
|
402
|
-
try {
|
|
403
|
-
const userDetails = await fetchUserDetails(currentSession.user);
|
|
404
|
-
if (mounted) setUser(userDetails);
|
|
405
|
-
} catch (err) {
|
|
406
|
-
console.error("Error fetching user details on auth state change:", err);
|
|
407
|
-
if (mounted) {
|
|
408
|
-
setUser({
|
|
409
|
-
id: currentSession.user.id,
|
|
410
|
-
email: currentSession.user.email
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (mounted) setLoading(false);
|
|
416
|
-
});
|
|
417
|
-
return () => {
|
|
418
|
-
mounted = false;
|
|
419
|
-
clearTimeout(safetyTimeout);
|
|
420
|
-
subscription?.unsubscribe();
|
|
421
|
-
};
|
|
422
|
-
}, [supabase, fetchUserDetails]);
|
|
423
|
-
const signOut = async () => {
|
|
424
|
-
if (!supabase) return;
|
|
425
|
-
setLoading(true);
|
|
426
|
-
const { error: signOutError } = await supabase.auth.signOut();
|
|
427
|
-
if (signOutError) setError(signOutError);
|
|
428
|
-
const logoutRedirectPath = authConfig?.defaultLogoutRedirect || "/login";
|
|
429
|
-
if (router$1 && router$1.pathname !== logoutRedirectPath && !router$1.pathname.startsWith(logoutRedirectPath)) {
|
|
430
|
-
router$1.replace(logoutRedirectPath);
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AuthContext.Provider, { value: { session, user, loading, error, signOut }, children });
|
|
434
|
-
};
|
|
435
|
-
var defaultContextValue = {
|
|
436
|
-
components: {},
|
|
437
|
-
hooks: {},
|
|
438
|
-
pages: {}
|
|
439
|
-
};
|
|
440
|
-
var DashboardOverridesContext = React14.createContext(defaultContextValue);
|
|
441
|
-
var DashboardOverridesProvider = ({
|
|
442
|
-
overrides = defaultContextValue,
|
|
443
|
-
children
|
|
444
|
-
}) => {
|
|
445
|
-
const normalizedOverrides = React14.useMemo(() => {
|
|
446
|
-
return {
|
|
447
|
-
components: overrides.components || {},
|
|
448
|
-
hooks: overrides.hooks || {},
|
|
449
|
-
pages: overrides.pages || {}
|
|
450
|
-
};
|
|
451
|
-
}, [overrides]);
|
|
452
|
-
return /* @__PURE__ */ jsxRuntime.jsx(DashboardOverridesContext.Provider, { value: normalizedOverrides, children });
|
|
453
|
-
};
|
|
454
|
-
function useComponentOverride(key, Default) {
|
|
455
|
-
const { components = {} } = React14.useContext(DashboardOverridesContext);
|
|
456
|
-
return components[key] ?? Default;
|
|
457
|
-
}
|
|
458
|
-
function useHookOverride(key, Default) {
|
|
459
|
-
const { hooks = {} } = React14.useContext(DashboardOverridesContext);
|
|
460
|
-
return hooks[key] ?? Default;
|
|
461
|
-
}
|
|
462
|
-
function usePageOverride(key, Default) {
|
|
463
|
-
const { pages = {} } = React14.useContext(DashboardOverridesContext);
|
|
464
|
-
return pages[key] ?? Default;
|
|
465
|
-
}
|
|
466
|
-
function useOverrides() {
|
|
467
|
-
return React14.useContext(DashboardOverridesContext);
|
|
309
|
+
function useVideoConfig() {
|
|
310
|
+
const { videoConfig } = useDashboardConfig();
|
|
311
|
+
return videoConfig ?? DEFAULT_VIDEO_CONFIG;
|
|
468
312
|
}
|
|
469
313
|
|
|
470
314
|
// src/lib/internal/supabaseClientInstance.ts
|
|
@@ -480,21 +324,31 @@ var _getSupabaseInstance = () => {
|
|
|
480
324
|
}
|
|
481
325
|
return supabaseInstance;
|
|
482
326
|
};
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const contextValue = React14.useMemo(() => ({ supabase: client }), [client]);
|
|
490
|
-
return /* @__PURE__ */ jsxRuntime.jsx(SupabaseContext.Provider, { value: contextValue, children });
|
|
327
|
+
|
|
328
|
+
// src/lib/services/actionService.ts
|
|
329
|
+
var getTable = (dbConfig, tableName) => {
|
|
330
|
+
const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
|
|
331
|
+
const userValue = dbConfig?.tables?.[tableName];
|
|
332
|
+
return userValue ?? defaults2[tableName];
|
|
491
333
|
};
|
|
492
|
-
var
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
334
|
+
var actionService = {
|
|
335
|
+
async getActionsByName(actionNames, companyIdInput) {
|
|
336
|
+
const supabase = _getSupabaseInstance();
|
|
337
|
+
const config = _getDashboardConfigInstance();
|
|
338
|
+
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
339
|
+
const entityConfig = config.entityConfig;
|
|
340
|
+
const actionsTable = getTable(dbConfig, "actions");
|
|
341
|
+
const targetCompanyId = companyIdInput ?? entityConfig?.companyId;
|
|
342
|
+
if (!targetCompanyId) {
|
|
343
|
+
throw new Error("Company ID must be provided either via entityConfig.companyId or as an argument to getActionsByName.");
|
|
344
|
+
}
|
|
345
|
+
const { data, error } = await supabase.from(actionsTable).select("id, action_name, company_id").eq("company_id", targetCompanyId).in("action_name", actionNames);
|
|
346
|
+
if (error) {
|
|
347
|
+
console.error(`Error fetching actions from table ${actionsTable}:`, error);
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
return data || [];
|
|
496
351
|
}
|
|
497
|
-
return context.supabase;
|
|
498
352
|
};
|
|
499
353
|
var getOperationalDate = (timezone = "Asia/Kolkata", date = /* @__PURE__ */ new Date(), shiftStartTime = "06:00") => {
|
|
500
354
|
const zonedDate = dateFnsTz.toZonedTime(date, timezone);
|
|
@@ -517,301 +371,57 @@ function getCurrentTimeInZone(timezone, formatString) {
|
|
|
517
371
|
return now2;
|
|
518
372
|
}
|
|
519
373
|
|
|
520
|
-
// src/lib/utils/
|
|
521
|
-
var
|
|
522
|
-
|
|
523
|
-
|
|
374
|
+
// src/lib/utils/shifts.ts
|
|
375
|
+
var DEFAULT_DAY_SHIFT_START = "06:00";
|
|
376
|
+
var DEFAULT_NIGHT_SHIFT_START = "18:00";
|
|
377
|
+
var DEFAULT_TRANSITION_MINUTES = 15;
|
|
378
|
+
var parseTimeToMinutes = (timeString) => {
|
|
379
|
+
if (!timeString || !/^[0-2]\d:[0-5]\d$/.test(timeString)) {
|
|
380
|
+
return NaN;
|
|
381
|
+
}
|
|
382
|
+
const [hours, minutes] = timeString.split(":").map(Number);
|
|
383
|
+
return hours * 60 + minutes;
|
|
524
384
|
};
|
|
525
|
-
var
|
|
526
|
-
|
|
385
|
+
var getCurrentShift = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
|
|
386
|
+
const dayShiftStartStr = shiftConfig?.dayShift?.startTime || DEFAULT_DAY_SHIFT_START;
|
|
387
|
+
const nightShiftStartStr = shiftConfig?.nightShift?.startTime || DEFAULT_NIGHT_SHIFT_START;
|
|
388
|
+
const dayShiftId = shiftConfig?.dayShift?.id ?? 0;
|
|
389
|
+
const nightShiftId = shiftConfig?.nightShift?.id ?? 1;
|
|
390
|
+
const dayShiftStartMinutes = parseTimeToMinutes(dayShiftStartStr);
|
|
391
|
+
const nightShiftStartMinutes = parseTimeToMinutes(nightShiftStartStr);
|
|
392
|
+
const effectiveDayStart = !isNaN(dayShiftStartMinutes) ? dayShiftStartMinutes : parseTimeToMinutes(DEFAULT_DAY_SHIFT_START);
|
|
393
|
+
const effectiveNightStart = !isNaN(nightShiftStartMinutes) ? nightShiftStartMinutes : parseTimeToMinutes(DEFAULT_NIGHT_SHIFT_START);
|
|
394
|
+
const zonedNow = dateFnsTz.toZonedTime(now2, timezone);
|
|
395
|
+
const currentHour = zonedNow.getHours();
|
|
396
|
+
const currentMinutes = zonedNow.getMinutes();
|
|
397
|
+
const currentTotalMinutes = currentHour * 60 + currentMinutes;
|
|
398
|
+
const operationalDate = getOperationalDate(timezone, zonedNow, dayShiftStartStr);
|
|
399
|
+
let determinedShiftId;
|
|
400
|
+
if (effectiveDayStart < effectiveNightStart) {
|
|
401
|
+
if (currentTotalMinutes >= effectiveDayStart && currentTotalMinutes < effectiveNightStart) {
|
|
402
|
+
determinedShiftId = dayShiftId;
|
|
403
|
+
} else {
|
|
404
|
+
determinedShiftId = nightShiftId;
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
if (currentTotalMinutes >= effectiveDayStart || currentTotalMinutes < effectiveNightStart) {
|
|
408
|
+
determinedShiftId = dayShiftId;
|
|
409
|
+
} else {
|
|
410
|
+
determinedShiftId = nightShiftId;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return { shiftId: determinedShiftId, date: operationalDate };
|
|
527
414
|
};
|
|
528
|
-
|
|
529
|
-
// src/lib/hooks/useMetrics.ts
|
|
530
|
-
var DEFAULT_COMPANY_ID = "default-company-id";
|
|
531
|
-
var useWorkspaceMetrics = (workspaceId) => {
|
|
532
|
-
const supabase = useSupabase();
|
|
533
|
-
const entityConfig = useEntityConfig();
|
|
534
|
-
useDatabaseConfig();
|
|
535
|
-
const dateTimeConfig = useDateTimeConfig();
|
|
536
|
-
const [workspaceMetrics, setWorkspaceMetrics] = React14.useState(null);
|
|
537
|
-
const [isLoading, setIsLoading] = React14.useState(true);
|
|
538
|
-
const [error, setError] = React14.useState(null);
|
|
539
|
-
const fetchWorkspaceMetrics = React14.useCallback(async () => {
|
|
540
|
-
try {
|
|
541
|
-
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
542
|
-
const { data, error: fetchError } = await supabase.from("overview_workspace_metrics").select("*").eq("workspace_id", workspaceId).eq("date", operationalDate).single();
|
|
543
|
-
if (fetchError) throw fetchError;
|
|
544
|
-
setWorkspaceMetrics(data);
|
|
545
|
-
} catch (err) {
|
|
546
|
-
setError({ message: err.message, code: err.code });
|
|
547
|
-
console.error("Error fetching workspace metrics:", err);
|
|
548
|
-
} finally {
|
|
549
|
-
setIsLoading(false);
|
|
550
|
-
}
|
|
551
|
-
}, [supabase, workspaceId, dateTimeConfig.defaultTimezone]);
|
|
552
|
-
React14.useEffect(() => {
|
|
553
|
-
let channels = [];
|
|
554
|
-
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
555
|
-
const setupSubscriptions = () => {
|
|
556
|
-
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
557
|
-
const metricsTablePrefix = getMetricsTablePrefix();
|
|
558
|
-
const metricsTable = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
559
|
-
const metricsChannel = supabase.channel("workspace-metrics").on(
|
|
560
|
-
"postgres_changes",
|
|
561
|
-
{
|
|
562
|
-
event: "*",
|
|
563
|
-
schema: "public",
|
|
564
|
-
table: metricsTable,
|
|
565
|
-
filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
|
|
566
|
-
},
|
|
567
|
-
async (payload) => {
|
|
568
|
-
console.log(`Received ${metricsTablePrefix} update:`, payload);
|
|
569
|
-
await fetchWorkspaceMetrics();
|
|
570
|
-
}
|
|
571
|
-
).subscribe((status) => {
|
|
572
|
-
console.log(`${metricsTablePrefix} subscription status:`, status);
|
|
573
|
-
});
|
|
574
|
-
const overviewChannel = supabase.channel("workspace-overview-metrics").on(
|
|
575
|
-
"postgres_changes",
|
|
576
|
-
{
|
|
577
|
-
event: "*",
|
|
578
|
-
schema: "public",
|
|
579
|
-
table: "overview_workspace_metrics",
|
|
580
|
-
filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
|
|
581
|
-
},
|
|
582
|
-
async (payload) => {
|
|
583
|
-
console.log("Received overview metrics update:", payload);
|
|
584
|
-
await fetchWorkspaceMetrics();
|
|
585
|
-
}
|
|
586
|
-
).subscribe((status) => {
|
|
587
|
-
console.log("Overview metrics subscription status:", status);
|
|
588
|
-
});
|
|
589
|
-
channels = [metricsChannel, overviewChannel];
|
|
590
|
-
};
|
|
591
|
-
fetchWorkspaceMetrics();
|
|
592
|
-
setupSubscriptions();
|
|
593
|
-
return () => {
|
|
594
|
-
channels.forEach((channel) => {
|
|
595
|
-
console.log("Cleaning up channel subscription");
|
|
596
|
-
supabase.removeChannel(channel);
|
|
597
|
-
});
|
|
598
|
-
};
|
|
599
|
-
}, [supabase, workspaceId, fetchWorkspaceMetrics, entityConfig.companyId, dateTimeConfig.defaultTimezone]);
|
|
600
|
-
return { workspaceMetrics, isLoading, error, refetch: fetchWorkspaceMetrics };
|
|
601
|
-
};
|
|
602
|
-
var useLineMetrics = (lineId) => {
|
|
603
|
-
const supabase = useSupabase();
|
|
604
|
-
const dateTimeConfig = useDateTimeConfig();
|
|
605
|
-
const [lineMetrics, setLineMetrics] = React14.useState(null);
|
|
606
|
-
const [isLoading, setIsLoading] = React14.useState(true);
|
|
607
|
-
const [error, setError] = React14.useState(null);
|
|
608
|
-
const fetchLineMetrics = React14.useCallback(async () => {
|
|
609
|
-
try {
|
|
610
|
-
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
611
|
-
const { data, error: fetchError } = await supabase.from("overview_line_metrics").select("*").eq("line_id", lineId).eq("date", operationalDate).single();
|
|
612
|
-
if (fetchError) throw fetchError;
|
|
613
|
-
setLineMetrics(data);
|
|
614
|
-
} catch (err) {
|
|
615
|
-
setError({ message: err.message, code: err.code });
|
|
616
|
-
console.error("Error fetching line metrics:", err);
|
|
617
|
-
} finally {
|
|
618
|
-
setIsLoading(false);
|
|
619
|
-
}
|
|
620
|
-
}, [supabase, lineId, dateTimeConfig.defaultTimezone]);
|
|
621
|
-
React14.useEffect(() => {
|
|
622
|
-
let channels = [];
|
|
623
|
-
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
624
|
-
const setupSubscriptions = () => {
|
|
625
|
-
const lineMetricsChannel = supabase.channel("line-base-metrics").on(
|
|
626
|
-
"postgres_changes",
|
|
627
|
-
{
|
|
628
|
-
event: "*",
|
|
629
|
-
schema: "public",
|
|
630
|
-
table: "line_metrics",
|
|
631
|
-
filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
|
|
632
|
-
},
|
|
633
|
-
async (payload) => {
|
|
634
|
-
console.log("Received line metrics update:", payload);
|
|
635
|
-
await fetchLineMetrics();
|
|
636
|
-
}
|
|
637
|
-
).subscribe((status) => {
|
|
638
|
-
console.log("Line metrics subscription status:", status);
|
|
639
|
-
});
|
|
640
|
-
const overviewChannel = supabase.channel("line-overview-metrics").on(
|
|
641
|
-
"postgres_changes",
|
|
642
|
-
{
|
|
643
|
-
event: "*",
|
|
644
|
-
schema: "public",
|
|
645
|
-
table: "overview_line_metrics",
|
|
646
|
-
filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
|
|
647
|
-
},
|
|
648
|
-
async (payload) => {
|
|
649
|
-
console.log("Received line overview update:", payload);
|
|
650
|
-
await fetchLineMetrics();
|
|
651
|
-
}
|
|
652
|
-
).subscribe((status) => {
|
|
653
|
-
console.log("Line overview subscription status:", status);
|
|
654
|
-
});
|
|
655
|
-
channels = [lineMetricsChannel, overviewChannel];
|
|
656
|
-
};
|
|
657
|
-
fetchLineMetrics();
|
|
658
|
-
setupSubscriptions();
|
|
659
|
-
return () => {
|
|
660
|
-
channels.forEach((channel) => {
|
|
661
|
-
console.log("Cleaning up channel subscription");
|
|
662
|
-
supabase.removeChannel(channel);
|
|
663
|
-
});
|
|
664
|
-
};
|
|
665
|
-
}, [supabase, lineId, fetchLineMetrics, dateTimeConfig.defaultTimezone]);
|
|
666
|
-
return { lineMetrics, isLoading, error, refetch: fetchLineMetrics };
|
|
667
|
-
};
|
|
668
|
-
var useMetrics = (tableName, options) => {
|
|
669
|
-
const supabase = useSupabase();
|
|
670
|
-
const entityConfig = useEntityConfig();
|
|
671
|
-
const [data, setData] = React14.useState([]);
|
|
672
|
-
const [isLoading, setIsLoading] = React14.useState(true);
|
|
673
|
-
const [error, setError] = React14.useState(null);
|
|
674
|
-
const channelRef = React14.useRef(null);
|
|
675
|
-
React14.useEffect(() => {
|
|
676
|
-
const fetchData = async () => {
|
|
677
|
-
try {
|
|
678
|
-
setIsLoading(true);
|
|
679
|
-
setError(null);
|
|
680
|
-
let actualTableName = tableName;
|
|
681
|
-
if (tableName === "metrics") {
|
|
682
|
-
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
683
|
-
const metricsTablePrefix = getMetricsTablePrefix(companyId);
|
|
684
|
-
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
685
|
-
}
|
|
686
|
-
let query = supabase.from(actualTableName).select("*");
|
|
687
|
-
if (options?.filter) {
|
|
688
|
-
Object.entries(options.filter).forEach(([key, value]) => {
|
|
689
|
-
query = query.eq(key, value);
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
const { data: result, error: fetchError } = await query;
|
|
693
|
-
if (fetchError) throw fetchError;
|
|
694
|
-
setData(result);
|
|
695
|
-
} catch (err) {
|
|
696
|
-
console.error(`Error fetching data from ${tableName}:`, err);
|
|
697
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
698
|
-
} finally {
|
|
699
|
-
setIsLoading(false);
|
|
700
|
-
}
|
|
701
|
-
};
|
|
702
|
-
const setupSubscription = () => {
|
|
703
|
-
if (!options?.realtime) return;
|
|
704
|
-
let actualTableName = tableName;
|
|
705
|
-
if (tableName === "metrics") {
|
|
706
|
-
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
707
|
-
const metricsTablePrefix = getMetricsTablePrefix();
|
|
708
|
-
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
709
|
-
}
|
|
710
|
-
const filter2 = {};
|
|
711
|
-
if (options?.filter) {
|
|
712
|
-
Object.entries(options.filter).forEach(([key, value]) => {
|
|
713
|
-
filter2[`${key}=eq.${value}`] = value;
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
channelRef.current = supabase.channel(`${tableName}-changes`).on(
|
|
717
|
-
"postgres_changes",
|
|
718
|
-
{
|
|
719
|
-
event: "*",
|
|
720
|
-
schema: "public",
|
|
721
|
-
table: actualTableName,
|
|
722
|
-
filter: Object.keys(filter2).length > 0 ? Object.keys(filter2).join(",") : void 0
|
|
723
|
-
},
|
|
724
|
-
() => {
|
|
725
|
-
fetchData();
|
|
726
|
-
}
|
|
727
|
-
).subscribe();
|
|
728
|
-
};
|
|
729
|
-
fetchData();
|
|
730
|
-
setupSubscription();
|
|
731
|
-
return () => {
|
|
732
|
-
if (channelRef.current) {
|
|
733
|
-
supabase.removeChannel(channelRef.current);
|
|
734
|
-
}
|
|
735
|
-
};
|
|
736
|
-
}, [supabase, tableName, options, entityConfig.companyId]);
|
|
737
|
-
const refetch = async () => {
|
|
738
|
-
setIsLoading(true);
|
|
739
|
-
try {
|
|
740
|
-
let actualTableName = tableName;
|
|
741
|
-
if (tableName === "metrics") {
|
|
742
|
-
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
743
|
-
const metricsTablePrefix = getMetricsTablePrefix(companyId);
|
|
744
|
-
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
745
|
-
}
|
|
746
|
-
let query = supabase.from(actualTableName).select("*");
|
|
747
|
-
if (options?.filter) {
|
|
748
|
-
Object.entries(options.filter).forEach(([key, value]) => {
|
|
749
|
-
query = query.eq(key, value);
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
const { data: result, error: fetchError } = await query;
|
|
753
|
-
if (fetchError) throw fetchError;
|
|
754
|
-
setData(result);
|
|
755
|
-
setError(null);
|
|
756
|
-
} catch (err) {
|
|
757
|
-
console.error(`Error refetching data from ${tableName}:`, err);
|
|
758
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
759
|
-
} finally {
|
|
760
|
-
setIsLoading(false);
|
|
761
|
-
}
|
|
762
|
-
};
|
|
763
|
-
return { data, isLoading, error, refetch };
|
|
764
|
-
};
|
|
765
|
-
var DEFAULT_DAY_SHIFT_START = "06:00";
|
|
766
|
-
var DEFAULT_NIGHT_SHIFT_START = "18:00";
|
|
767
|
-
var DEFAULT_TRANSITION_MINUTES = 15;
|
|
768
|
-
var parseTimeToMinutes = (timeString) => {
|
|
769
|
-
if (!timeString || !/^[0-2]\d:[0-5]\d$/.test(timeString)) {
|
|
770
|
-
return NaN;
|
|
771
|
-
}
|
|
772
|
-
const [hours, minutes] = timeString.split(":").map(Number);
|
|
773
|
-
return hours * 60 + minutes;
|
|
774
|
-
};
|
|
775
|
-
var getCurrentShift = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
|
|
415
|
+
var isTransitionPeriod = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
|
|
776
416
|
const dayShiftStartStr = shiftConfig?.dayShift?.startTime || DEFAULT_DAY_SHIFT_START;
|
|
777
417
|
const nightShiftStartStr = shiftConfig?.nightShift?.startTime || DEFAULT_NIGHT_SHIFT_START;
|
|
778
|
-
const
|
|
779
|
-
const nightShiftId = shiftConfig?.nightShift?.id ?? 1;
|
|
418
|
+
const transitionMinutes = shiftConfig?.transitionPeriodMinutes ?? DEFAULT_TRANSITION_MINUTES;
|
|
780
419
|
const dayShiftStartMinutes = parseTimeToMinutes(dayShiftStartStr);
|
|
781
420
|
const nightShiftStartMinutes = parseTimeToMinutes(nightShiftStartStr);
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
const
|
|
786
|
-
const currentMinutes = zonedNow.getMinutes();
|
|
787
|
-
const currentTotalMinutes = currentHour * 60 + currentMinutes;
|
|
788
|
-
const operationalDate = getOperationalDate(timezone, zonedNow, dayShiftStartStr);
|
|
789
|
-
let determinedShiftId;
|
|
790
|
-
if (effectiveDayStart < effectiveNightStart) {
|
|
791
|
-
if (currentTotalMinutes >= effectiveDayStart && currentTotalMinutes < effectiveNightStart) {
|
|
792
|
-
determinedShiftId = dayShiftId;
|
|
793
|
-
} else {
|
|
794
|
-
determinedShiftId = nightShiftId;
|
|
795
|
-
}
|
|
796
|
-
} else {
|
|
797
|
-
if (currentTotalMinutes >= effectiveDayStart || currentTotalMinutes < effectiveNightStart) {
|
|
798
|
-
determinedShiftId = dayShiftId;
|
|
799
|
-
} else {
|
|
800
|
-
determinedShiftId = nightShiftId;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
return { shiftId: determinedShiftId, date: operationalDate };
|
|
804
|
-
};
|
|
805
|
-
var isTransitionPeriod = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
|
|
806
|
-
const dayShiftStartStr = shiftConfig?.dayShift?.startTime || DEFAULT_DAY_SHIFT_START;
|
|
807
|
-
const nightShiftStartStr = shiftConfig?.nightShift?.startTime || DEFAULT_NIGHT_SHIFT_START;
|
|
808
|
-
const transitionMinutes = shiftConfig?.transitionPeriodMinutes ?? DEFAULT_TRANSITION_MINUTES;
|
|
809
|
-
const dayShiftStartMinutes = parseTimeToMinutes(dayShiftStartStr);
|
|
810
|
-
const nightShiftStartMinutes = parseTimeToMinutes(nightShiftStartStr);
|
|
811
|
-
if (isNaN(dayShiftStartMinutes) || isNaN(nightShiftStartMinutes)) {
|
|
812
|
-
return false;
|
|
813
|
-
}
|
|
814
|
-
const transitionTimes = [dayShiftStartMinutes, nightShiftStartMinutes];
|
|
421
|
+
if (isNaN(dayShiftStartMinutes) || isNaN(nightShiftStartMinutes)) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
const transitionTimes = [dayShiftStartMinutes, nightShiftStartMinutes];
|
|
815
425
|
const zonedNow = dateFnsTz.toZonedTime(now2, timezone);
|
|
816
426
|
const currentHour = zonedNow.getHours();
|
|
817
427
|
const currentMinutes = zonedNow.getMinutes();
|
|
@@ -837,748 +447,246 @@ var isTransitionPeriod = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date
|
|
|
837
447
|
});
|
|
838
448
|
};
|
|
839
449
|
|
|
840
|
-
// src/lib/
|
|
841
|
-
var
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
const
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
console.log(`[useWorkspaceDetailedMetrics] Found fallback data from date: ${recentData.date}, shift: ${recentData.shift_id}`);
|
|
880
|
-
const outputDifference2 = (recentData.total_output || 0) - (recentData.ideal_output || 0);
|
|
881
|
-
const workspaceMatch2 = recentData.workspace_name?.match(/WS(\d+)/);
|
|
882
|
-
const workspaceNumber2 = workspaceMatch2 ? parseInt(workspaceMatch2[1]) : 0;
|
|
883
|
-
const specialWsStart2 = workspaceConfig.specialWorkspaces?.startId ?? 19;
|
|
884
|
-
const specialWsEnd2 = workspaceConfig.specialWorkspaces?.endId ?? 34;
|
|
885
|
-
const isSpecialWorkspace2 = workspaceNumber2 >= specialWsStart2 && workspaceNumber2 <= specialWsEnd2;
|
|
886
|
-
const outputHourly2 = recentData.output_hourly || {};
|
|
887
|
-
const hasOutputHourlyData2 = outputHourly2 && typeof outputHourly2 === "object" && Object.keys(outputHourly2).length > 0;
|
|
888
|
-
let hourlyActionCounts2 = [];
|
|
889
|
-
if (hasOutputHourlyData2) {
|
|
890
|
-
console.log("Using new output_hourly column for workspace (fallback):", recentData.workspace_name);
|
|
891
|
-
console.log("Raw output_hourly data (fallback):", outputHourly2);
|
|
892
|
-
const isNightShift = recentData.shift_id === 1;
|
|
893
|
-
const shiftStart = recentData.shift_start || (isNightShift ? "22:00" : "06:00");
|
|
894
|
-
const shiftEnd = recentData.shift_end || (isNightShift ? "06:00" : "14:00");
|
|
895
|
-
const startHour = parseInt(shiftStart.split(":")[0]);
|
|
896
|
-
let expectedHours = [];
|
|
897
|
-
if (isNightShift) {
|
|
898
|
-
for (let i = 0; i < 9; i++) {
|
|
899
|
-
expectedHours.push((startHour + i) % 24);
|
|
900
|
-
}
|
|
901
|
-
} else {
|
|
902
|
-
for (let i = 0; i < 9; i++) {
|
|
903
|
-
expectedHours.push((startHour + i) % 24);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
console.log("Expected shift hours (fallback):", expectedHours);
|
|
907
|
-
console.log("Available data hours (fallback):", Object.keys(outputHourly2));
|
|
908
|
-
hourlyActionCounts2 = expectedHours.map((expectedHour) => {
|
|
909
|
-
let hourData = outputHourly2[expectedHour.toString()];
|
|
910
|
-
if (!hourData && isNightShift) {
|
|
911
|
-
for (const [storedHour, data2] of Object.entries(outputHourly2)) {
|
|
912
|
-
if (Array.isArray(data2) && data2.length > 0 && data2.some((val) => val > 0)) {
|
|
913
|
-
if (storedHour === "18" && expectedHour === 1) {
|
|
914
|
-
hourData = data2;
|
|
915
|
-
console.log(`Mapping stored hour ${storedHour} to expected hour ${expectedHour} (fallback)`);
|
|
916
|
-
break;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
return Array.isArray(hourData) ? hourData.reduce((sum, count) => sum + (count || 0), 0) : 0;
|
|
922
|
-
});
|
|
923
|
-
console.log("Final hourly action counts (fallback):", hourlyActionCounts2);
|
|
924
|
-
} else {
|
|
925
|
-
console.log("Using output_array fallback for workspace (fallback):", recentData.workspace_name);
|
|
926
|
-
const minuteByMinuteArray = recentData.output_array || [];
|
|
927
|
-
if (isSpecialWorkspace2) {
|
|
928
|
-
const last40Readings = minuteByMinuteArray.slice(Math.max(0, minuteByMinuteArray.length - 40));
|
|
929
|
-
hourlyActionCounts2 = last40Readings;
|
|
930
|
-
} else {
|
|
931
|
-
for (let i = 0; i < minuteByMinuteArray.length; i += 60) {
|
|
932
|
-
const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
|
|
933
|
-
const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
|
|
934
|
-
hourlyActionCounts2.push(hourlySum);
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
const transformedData2 = {
|
|
939
|
-
workspace_id: recentData.workspace_id,
|
|
940
|
-
workspace_name: recentData.workspace_name,
|
|
941
|
-
line_id: recentData.line_id,
|
|
942
|
-
line_name: recentData.line_name || "Line 1",
|
|
943
|
-
company_id: recentData.company_id || companyId,
|
|
944
|
-
company_name: recentData.company_name || "Nahar Group",
|
|
945
|
-
date: recentData.date,
|
|
946
|
-
shift_id: recentData.shift_id,
|
|
947
|
-
action_name: recentData.action_name || "",
|
|
948
|
-
shift_start: recentData.shift_start || "06:00",
|
|
949
|
-
shift_end: recentData.shift_end || "14:00",
|
|
950
|
-
shift_type: recentData.shift_type || (recentData.shift_id === 0 ? "Day" : "Night"),
|
|
951
|
-
pph_threshold: recentData.pph_threshold || 0,
|
|
952
|
-
target_output: recentData.total_day_output || 0,
|
|
953
|
-
avg_pph: recentData.avg_pph || 0,
|
|
954
|
-
avg_cycle_time: recentData.avg_cycle_time || 0,
|
|
955
|
-
ideal_cycle_time: recentData.ideal_cycle_time || 0,
|
|
956
|
-
avg_efficiency: recentData.efficiency || 0,
|
|
957
|
-
total_actions: recentData.total_output || 0,
|
|
958
|
-
hourly_action_counts: hourlyActionCounts2,
|
|
959
|
-
// Now uses the NEW logic with fallback
|
|
960
|
-
workspace_rank: recentData.workspace_rank || 0,
|
|
961
|
-
total_workspaces: recentData.total_workspaces || workspaceConfig.totalWorkspaces || 42,
|
|
962
|
-
ideal_output_until_now: recentData.ideal_output || 0,
|
|
963
|
-
output_difference: outputDifference2,
|
|
964
|
-
idle_time: recentData.idle_time || 0,
|
|
965
|
-
idle_time_hourly: recentData.idle_time_hourly || void 0,
|
|
966
|
-
...recentData.compliance_efficiency !== void 0 && { compliance_efficiency: recentData.compliance_efficiency },
|
|
967
|
-
...recentData.sop_check !== void 0 && { sop_check: recentData.sop_check }
|
|
968
|
-
};
|
|
969
|
-
setMetrics(transformedData2);
|
|
970
|
-
setIsLoading(false);
|
|
971
|
-
updateQueueRef.current = false;
|
|
972
|
-
isFetchingRef.current = false;
|
|
973
|
-
return;
|
|
974
|
-
} else {
|
|
975
|
-
console.warn("[useWorkspaceDetailedMetrics] No data found for workspace:", workspaceId, "at all");
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
if (!data) {
|
|
979
|
-
console.warn("[useWorkspaceDetailedMetrics] No detailed metrics found for workspace:", workspaceId);
|
|
980
|
-
setMetrics(null);
|
|
981
|
-
setIsLoading(false);
|
|
982
|
-
updateQueueRef.current = false;
|
|
983
|
-
isFetchingRef.current = false;
|
|
984
|
-
return;
|
|
450
|
+
// src/lib/utils/database.ts
|
|
451
|
+
var getCompanyMetricsTableName = (companyId, prefix = "workspace_performance") => {
|
|
452
|
+
if (!companyId) return `${prefix}_unknown_company`;
|
|
453
|
+
return `${prefix}_${companyId.replace(/-/g, "_")}`;
|
|
454
|
+
};
|
|
455
|
+
var getMetricsTablePrefix = (companyId) => {
|
|
456
|
+
return "performance_metrics";
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// src/lib/services/dashboardService.ts
|
|
460
|
+
var getTable2 = (dbConfig, tableName) => {
|
|
461
|
+
const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
|
|
462
|
+
const userValue = dbConfig?.tables?.[tableName];
|
|
463
|
+
return userValue ?? defaults2[tableName];
|
|
464
|
+
};
|
|
465
|
+
var dashboardService = {
|
|
466
|
+
// Example for getLineInfo:
|
|
467
|
+
async getLineInfo(lineIdInput) {
|
|
468
|
+
const supabase = _getSupabaseInstance();
|
|
469
|
+
const config = _getDashboardConfigInstance();
|
|
470
|
+
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
471
|
+
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
472
|
+
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
473
|
+
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
474
|
+
const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
|
|
475
|
+
const linesTable = getTable2(dbConfig, "lines");
|
|
476
|
+
const lineMetricsTable = getTable2(dbConfig, "lineMetrics");
|
|
477
|
+
const companyId = entityConfig.companyId;
|
|
478
|
+
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
479
|
+
const metricsTable = `${metricsTablePrefixStr}_${companyId ? companyId.replace(/-/g, "_") : "unknown_company"}`;
|
|
480
|
+
const defaultLineId = entityConfig.defaultLineId;
|
|
481
|
+
const secondaryLineId = entityConfig.secondaryLineId;
|
|
482
|
+
const factoryViewId = entityConfig.factoryViewId ?? "factory";
|
|
483
|
+
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
484
|
+
const { shiftId, date } = getCurrentShift(defaultTimezone, shiftConfig);
|
|
485
|
+
const lineId = lineIdInput;
|
|
486
|
+
if (lineId === factoryViewId) {
|
|
487
|
+
if (!defaultLineId || !secondaryLineId || !companyId) {
|
|
488
|
+
throw new Error("Factory View requires defaultLineId, secondaryLineId, and companyId to be configured.");
|
|
985
489
|
}
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
|
|
1047
|
-
const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
|
|
1048
|
-
hourlyActionCounts.push(hourlySum);
|
|
1049
|
-
}
|
|
490
|
+
const { data: lineData2, error: lineError2 } = await supabase.from(linesTable).select(`
|
|
491
|
+
id,
|
|
492
|
+
line_name,
|
|
493
|
+
factory_id,
|
|
494
|
+
factories!lines_factory_id_fkey(factory_name),
|
|
495
|
+
company_id,
|
|
496
|
+
companies!lines_company_id_fkey(company_name:name)
|
|
497
|
+
`).eq("id", defaultLineId).maybeSingle();
|
|
498
|
+
if (lineError2) throw lineError2;
|
|
499
|
+
if (!lineData2) throw new Error(`Configured default line (${defaultLineId}) not found`);
|
|
500
|
+
const { data: metricsData, error: metricsError } = await supabase.from(lineMetricsTable).select("*").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
|
|
501
|
+
if (metricsError) throw metricsError;
|
|
502
|
+
const { data: performanceData2, error: performanceError2 } = await supabase.from(metricsTable).select("efficiency").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
|
|
503
|
+
if (performanceError2) throw performanceError2;
|
|
504
|
+
const underperformingCount2 = performanceData2?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
|
|
505
|
+
const totalValidWorkspaces2 = performanceData2?.filter((w) => w.efficiency >= 10).length || 0;
|
|
506
|
+
const combinedMetrics = (metricsData || []).reduce((acc, m) => {
|
|
507
|
+
acc.avg_efficiency += m.efficiency ?? 0;
|
|
508
|
+
acc.current_output += m.current_output ?? 0;
|
|
509
|
+
acc.ideal_output += m.ideal_output ?? m.line_threshold ?? 0;
|
|
510
|
+
return acc;
|
|
511
|
+
}, { avg_efficiency: 0, current_output: 0, ideal_output: 0 });
|
|
512
|
+
metricsData?.length || 1;
|
|
513
|
+
return {
|
|
514
|
+
line_id: factoryViewId,
|
|
515
|
+
// Use configured factory view ID
|
|
516
|
+
line_name: "Factory View",
|
|
517
|
+
// Consider making this configurable?
|
|
518
|
+
company_id: lineData2.company_id,
|
|
519
|
+
company_name: lineData2.companies?.[0]?.company_name ?? "",
|
|
520
|
+
factory_id: lineData2.factory_id,
|
|
521
|
+
factory_name: lineData2.factories?.[0]?.factory_name ?? "",
|
|
522
|
+
shift_id: shiftId,
|
|
523
|
+
date,
|
|
524
|
+
metrics: {
|
|
525
|
+
avg_efficiency: (performanceData2?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces2 || 1),
|
|
526
|
+
// Use performance data for avg efficiency
|
|
527
|
+
avg_cycle_time: 0,
|
|
528
|
+
// Needs calculation logic if required for factory view
|
|
529
|
+
current_output: combinedMetrics.current_output,
|
|
530
|
+
ideal_output: combinedMetrics.ideal_output,
|
|
531
|
+
total_workspaces: 44,
|
|
532
|
+
// SRC ALIGNMENT: Use hardcoded 44 for factory view total_workspaces
|
|
533
|
+
underperforming_workspaces: underperformingCount2,
|
|
534
|
+
underperforming_workspace_names: [],
|
|
535
|
+
// Populate if needed
|
|
536
|
+
underperforming_workspace_uuids: [],
|
|
537
|
+
// Populate if needed
|
|
538
|
+
output_array: [],
|
|
539
|
+
// Combine if needed
|
|
540
|
+
line_threshold: combinedMetrics.ideal_output,
|
|
541
|
+
threshold_pph: 0,
|
|
542
|
+
// Needs calculation logic if required
|
|
543
|
+
shift_start: shiftConfig.dayShift?.startTime || "06:00",
|
|
544
|
+
// Use config
|
|
545
|
+
shift_end: shiftConfig.dayShift?.endTime || "18:00",
|
|
546
|
+
// Use config
|
|
547
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
548
|
+
poorest_performing_workspaces: []
|
|
549
|
+
// Populate if needed
|
|
1050
550
|
}
|
|
1051
|
-
console.log("Final hourly action counts:", hourlyActionCounts);
|
|
1052
|
-
}
|
|
1053
|
-
const transformedData = {
|
|
1054
|
-
workspace_id: data.workspace_id,
|
|
1055
|
-
workspace_name: data.workspace_name,
|
|
1056
|
-
line_id: data.line_id,
|
|
1057
|
-
line_name: data.line_name || "Line 1",
|
|
1058
|
-
company_id: data.company_id || companyId,
|
|
1059
|
-
company_name: data.company_name || "Nahar Group",
|
|
1060
|
-
date: data.date,
|
|
1061
|
-
shift_id: data.shift_id,
|
|
1062
|
-
action_name: data.action_name || "",
|
|
1063
|
-
shift_start: data.shift_start || "06:00",
|
|
1064
|
-
shift_end: data.shift_end || "14:00",
|
|
1065
|
-
shift_type: data.shift_type || (data.shift_id === 0 ? "Day" : "Night"),
|
|
1066
|
-
pph_threshold: data.pph_threshold || 0,
|
|
1067
|
-
target_output: data.total_day_output || 0,
|
|
1068
|
-
avg_pph: data.avg_pph || 0,
|
|
1069
|
-
avg_cycle_time: data.avg_cycle_time || 0,
|
|
1070
|
-
ideal_cycle_time: data.ideal_cycle_time || 0,
|
|
1071
|
-
avg_efficiency: data.efficiency || 0,
|
|
1072
|
-
total_actions: data.total_output || 0,
|
|
1073
|
-
hourly_action_counts: hourlyActionCounts,
|
|
1074
|
-
workspace_rank: data.workspace_rank || 0,
|
|
1075
|
-
total_workspaces: data.total_workspaces || workspaceConfig.totalWorkspaces || 42,
|
|
1076
|
-
ideal_output_until_now: data.ideal_output || 0,
|
|
1077
|
-
output_difference: outputDifference,
|
|
1078
|
-
idle_time: data.idle_time || 0,
|
|
1079
|
-
// Add idle_time from performance_metrics table
|
|
1080
|
-
idle_time_hourly: data.idle_time_hourly || void 0,
|
|
1081
|
-
// Add idle_time_hourly from performance_metrics table
|
|
1082
|
-
...data.compliance_efficiency !== void 0 && { compliance_efficiency: data.compliance_efficiency },
|
|
1083
|
-
...data.sop_check !== void 0 && { sop_check: data.sop_check }
|
|
1084
551
|
};
|
|
1085
|
-
|
|
552
|
+
}
|
|
553
|
+
if (!companyId) {
|
|
554
|
+
throw new Error("Company ID must be configured for individual line requests.");
|
|
555
|
+
}
|
|
556
|
+
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();
|
|
557
|
+
if (lineError) throw lineError;
|
|
558
|
+
if (!lineData) throw new Error(`Line with ID ${lineId} not found`);
|
|
559
|
+
let metricsFromDb = null;
|
|
560
|
+
try {
|
|
561
|
+
const { data: fetchedMetrics, error } = await supabase.from(lineMetricsTable).select("*").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date).maybeSingle();
|
|
562
|
+
if (error) throw error;
|
|
563
|
+
metricsFromDb = fetchedMetrics;
|
|
1086
564
|
} catch (err) {
|
|
1087
|
-
console.error(
|
|
1088
|
-
setError({ message: err.message, code: err.code });
|
|
1089
|
-
} finally {
|
|
1090
|
-
isFetchingRef.current = false;
|
|
1091
|
-
updateQueueRef.current = false;
|
|
1092
|
-
setIsLoading(false);
|
|
565
|
+
console.error(`Error fetching line metrics for ${lineId}:`, err);
|
|
1093
566
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
567
|
+
const { data: performanceData, error: performanceError } = await supabase.from(metricsTable).select("efficiency").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date);
|
|
568
|
+
if (performanceError) throw performanceError;
|
|
569
|
+
const underperformingCount = performanceData?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
|
|
570
|
+
const totalValidWorkspaces = performanceData?.filter((w) => w.efficiency >= 10).length || 0;
|
|
571
|
+
const avgEfficiencyFromPerf = (performanceData?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces || 1);
|
|
572
|
+
const useFallbackMetrics = !metricsFromDb;
|
|
573
|
+
let metricsForReturn;
|
|
574
|
+
if (useFallbackMetrics) {
|
|
575
|
+
metricsForReturn = {
|
|
576
|
+
avg_efficiency: 0,
|
|
577
|
+
avg_cycle_time: 0,
|
|
578
|
+
current_output: 0,
|
|
579
|
+
ideal_output: 0,
|
|
580
|
+
total_workspaces: 42,
|
|
581
|
+
underperforming_workspaces: underperformingCount,
|
|
582
|
+
underperforming_workspace_names: [],
|
|
583
|
+
underperforming_workspace_uuids: [],
|
|
584
|
+
output_array: [],
|
|
585
|
+
line_threshold: 0,
|
|
586
|
+
threshold_pph: 0,
|
|
587
|
+
shift_start: "06:00",
|
|
588
|
+
shift_end: "14:00",
|
|
589
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
590
|
+
poorest_performing_workspaces: []
|
|
591
|
+
};
|
|
592
|
+
} else {
|
|
593
|
+
metricsForReturn = {
|
|
594
|
+
avg_efficiency: metricsFromDb.efficiency ?? avgEfficiencyFromPerf,
|
|
595
|
+
avg_cycle_time: metricsFromDb.avg_cycle_time || 0,
|
|
596
|
+
current_output: metricsFromDb.current_output || 0,
|
|
597
|
+
ideal_output: metricsFromDb.ideal_output || metricsFromDb.line_threshold || 0,
|
|
598
|
+
total_workspaces: metricsFromDb.total_workspaces ?? workspaceConfig.totalWorkspaces ?? 42,
|
|
599
|
+
underperforming_workspaces: underperformingCount,
|
|
600
|
+
underperforming_workspace_names: metricsFromDb.underperforming_workspace_names || [],
|
|
601
|
+
underperforming_workspace_uuids: metricsFromDb.underperforming_workspace_uuids || [],
|
|
602
|
+
output_array: metricsFromDb.output_array || [],
|
|
603
|
+
line_threshold: metricsFromDb.line_threshold || 0,
|
|
604
|
+
threshold_pph: metricsFromDb.threshold_pph || 0,
|
|
605
|
+
shift_start: metricsFromDb.shift_start || shiftConfig.dayShift?.startTime || "06:00",
|
|
606
|
+
shift_end: metricsFromDb.shift_end || shiftConfig.dayShift?.endTime || "18:00",
|
|
607
|
+
last_updated: metricsFromDb.last_updated || (/* @__PURE__ */ new Date()).toISOString(),
|
|
608
|
+
poorest_performing_workspaces: metricsFromDb.poorest_performing_workspaces || []
|
|
609
|
+
};
|
|
610
|
+
if (metricsFromDb.efficiency === null || metricsFromDb.efficiency === void 0) {
|
|
611
|
+
metricsForReturn.avg_efficiency = avgEfficiencyFromPerf;
|
|
612
|
+
}
|
|
1100
613
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
614
|
+
return {
|
|
615
|
+
line_id: lineData.id,
|
|
616
|
+
line_name: lineData.line_name,
|
|
617
|
+
company_id: lineData.company_id,
|
|
618
|
+
company_name: lineData.companies?.[0]?.company_name ?? "",
|
|
619
|
+
factory_id: lineData.factory_id,
|
|
620
|
+
factory_name: lineData.factories?.[0]?.factory_name ?? "",
|
|
621
|
+
shift_id: shiftId,
|
|
622
|
+
date,
|
|
623
|
+
metrics: metricsForReturn
|
|
624
|
+
};
|
|
625
|
+
},
|
|
626
|
+
async getWorkspacesData(lineIdInput, dateProp, shiftProp) {
|
|
627
|
+
const supabase = _getSupabaseInstance();
|
|
628
|
+
const config = _getDashboardConfigInstance();
|
|
629
|
+
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
630
|
+
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
631
|
+
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
632
|
+
const companyId = entityConfig.companyId;
|
|
633
|
+
if (!companyId) {
|
|
634
|
+
throw new Error("Company ID must be configured for workspace data requests.");
|
|
1109
635
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
636
|
+
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
637
|
+
const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
|
|
638
|
+
const defaultLineId = entityConfig.defaultLineId;
|
|
639
|
+
const secondaryLineId = entityConfig.secondaryLineId;
|
|
640
|
+
const factoryViewId = entityConfig.factoryViewId ?? "factory";
|
|
641
|
+
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
642
|
+
const currentShiftResult = getCurrentShift(defaultTimezone, shiftConfig);
|
|
643
|
+
const queryDate = dateProp || getOperationalDate(defaultTimezone);
|
|
644
|
+
const queryShiftId = shiftProp ?? currentShiftResult.shiftId;
|
|
645
|
+
const lineId = lineIdInput;
|
|
646
|
+
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);
|
|
647
|
+
if (!lineId || lineId === factoryViewId) {
|
|
648
|
+
if (!defaultLineId || !secondaryLineId) {
|
|
649
|
+
throw new Error("Factory View requires defaultLineId and secondaryLineId to be configured for workspace data.");
|
|
1121
650
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
}, [supabase, workspaceId, fetchMetrics, schema, metricsTable, metricsTablePrefix]);
|
|
1126
|
-
React14.useEffect(() => {
|
|
1127
|
-
if (!workspaceId) {
|
|
1128
|
-
setIsLoading(false);
|
|
1129
|
-
return;
|
|
651
|
+
query = query.in("line_id", [defaultLineId, secondaryLineId]);
|
|
652
|
+
} else {
|
|
653
|
+
query = query.eq("line_id", lineId);
|
|
1130
654
|
}
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
const metricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
|
|
1136
|
-
"postgres_changes",
|
|
1137
|
-
{
|
|
1138
|
-
event: "*",
|
|
1139
|
-
schema,
|
|
1140
|
-
table: metricsTable,
|
|
1141
|
-
filter: `workspace_id=eq.${workspaceId}`
|
|
1142
|
-
},
|
|
1143
|
-
async (payload) => {
|
|
1144
|
-
const payloadData = payload.new;
|
|
1145
|
-
console.log(`Received ${metricsTablePrefix} update:`, {
|
|
1146
|
-
payload,
|
|
1147
|
-
payloadDate: payloadData?.date,
|
|
1148
|
-
payloadShift: payloadData?.shift_id,
|
|
1149
|
-
currentDate: operationalDate,
|
|
1150
|
-
currentShift: queryShiftId,
|
|
1151
|
-
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
1152
|
-
});
|
|
1153
|
-
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
1154
|
-
queueUpdate();
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
).subscribe((status) => {
|
|
1158
|
-
console.log(`${metricsTablePrefix} subscription status:`, status);
|
|
1159
|
-
});
|
|
1160
|
-
const workspaceMetricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
|
|
1161
|
-
"postgres_changes",
|
|
1162
|
-
{
|
|
1163
|
-
event: "*",
|
|
1164
|
-
schema,
|
|
1165
|
-
table: workspaceMetricsBaseTable,
|
|
1166
|
-
filter: `workspace_id=eq.${workspaceId}`
|
|
1167
|
-
},
|
|
1168
|
-
async (payload) => {
|
|
1169
|
-
const payloadData = payload.new;
|
|
1170
|
-
console.log("Received workspace_metrics update:", {
|
|
1171
|
-
payload,
|
|
1172
|
-
payloadDate: payloadData?.date,
|
|
1173
|
-
payloadShift: payloadData?.shift_id,
|
|
1174
|
-
currentDate: operationalDate,
|
|
1175
|
-
currentShift: queryShiftId,
|
|
1176
|
-
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
1177
|
-
});
|
|
1178
|
-
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
1179
|
-
queueUpdate();
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
).subscribe((status) => {
|
|
1183
|
-
console.log(`Workspace metrics subscription status:`, status);
|
|
1184
|
-
});
|
|
1185
|
-
const workspaceActionsChannel = supabase.channel(`workspace-actions-${workspaceId}`).on(
|
|
1186
|
-
"postgres_changes",
|
|
1187
|
-
{
|
|
1188
|
-
event: "*",
|
|
1189
|
-
schema,
|
|
1190
|
-
table: workspaceActionsTable,
|
|
1191
|
-
filter: `workspace_id=eq.${workspaceId}`
|
|
1192
|
-
},
|
|
1193
|
-
async (payload) => {
|
|
1194
|
-
const payloadData = payload.new;
|
|
1195
|
-
console.log("Received workspace_actions update:", {
|
|
1196
|
-
payload,
|
|
1197
|
-
payloadDate: payloadData?.date,
|
|
1198
|
-
payloadShift: payloadData?.shift_id,
|
|
1199
|
-
currentDate: operationalDate,
|
|
1200
|
-
currentShift: queryShiftId,
|
|
1201
|
-
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
1202
|
-
});
|
|
1203
|
-
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
1204
|
-
queueUpdate();
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
).subscribe((status) => {
|
|
1208
|
-
console.log(`Workspace actions subscription status:`, status);
|
|
1209
|
-
});
|
|
1210
|
-
channels.push(metricsChannel, workspaceMetricsChannel, workspaceActionsChannel);
|
|
1211
|
-
fetchMetrics();
|
|
1212
|
-
setupSubscription();
|
|
1213
|
-
return () => {
|
|
1214
|
-
if (timeoutRef.current) {
|
|
1215
|
-
clearTimeout(timeoutRef.current);
|
|
1216
|
-
}
|
|
1217
|
-
channels.forEach((channel) => {
|
|
1218
|
-
console.log("Cleaning up channel subscription");
|
|
1219
|
-
supabase.removeChannel(channel);
|
|
1220
|
-
});
|
|
1221
|
-
if (channelRef.current) {
|
|
1222
|
-
supabase.removeChannel(channelRef.current);
|
|
1223
|
-
}
|
|
1224
|
-
};
|
|
1225
|
-
}, [supabase, workspaceId, date, shiftId, fetchMetrics, queueUpdate, setupSubscription, metricsTable, workspaceMetricsBaseTable, workspaceActionsTable, defaultTimezone, shiftConfig, schema, metricsTablePrefix]);
|
|
1226
|
-
return {
|
|
1227
|
-
metrics: metrics2,
|
|
1228
|
-
isLoading,
|
|
1229
|
-
error,
|
|
1230
|
-
refetch: fetchMetrics
|
|
1231
|
-
};
|
|
1232
|
-
};
|
|
1233
|
-
var useLineWorkspaceMetrics = (lineId, options) => {
|
|
1234
|
-
const entityConfig = useEntityConfig();
|
|
1235
|
-
const databaseConfig = useDatabaseConfig();
|
|
1236
|
-
const dateTimeConfig = useDateTimeConfig();
|
|
1237
|
-
const shiftConfig = useShiftConfig();
|
|
1238
|
-
const supabase = useSupabase();
|
|
1239
|
-
const [workspaces, setWorkspaces] = React14.useState([]);
|
|
1240
|
-
const [loading, setLoading] = React14.useState(true);
|
|
1241
|
-
const [error, setError] = React14.useState(null);
|
|
1242
|
-
const [initialized, setInitialized] = React14.useState(false);
|
|
1243
|
-
const queryShiftId = React14.useMemo(() => {
|
|
1244
|
-
const currentShift = getCurrentShift(
|
|
1245
|
-
dateTimeConfig.defaultTimezone || "Asia/Kolkata",
|
|
1246
|
-
shiftConfig
|
|
1247
|
-
);
|
|
1248
|
-
return options?.initialShiftId !== void 0 ? options.initialShiftId : currentShift.shiftId;
|
|
1249
|
-
}, [options?.initialShiftId, dateTimeConfig.defaultTimezone, shiftConfig]);
|
|
1250
|
-
const queryDate = React14.useMemo(() => {
|
|
1251
|
-
return options?.initialDate || getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
1252
|
-
}, [options?.initialDate, dateTimeConfig.defaultTimezone]);
|
|
1253
|
-
const metricsTable = React14.useMemo(() => {
|
|
1254
|
-
const companyId = entityConfig.companyId;
|
|
1255
|
-
if (!companyId) return "";
|
|
1256
|
-
const metricsTablePrefix = getMetricsTablePrefix();
|
|
1257
|
-
return `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
1258
|
-
}, [entityConfig.companyId]);
|
|
1259
|
-
const schema = databaseConfig.schema ?? "public";
|
|
1260
|
-
const fetchWorkspaceMetrics = React14.useCallback(async () => {
|
|
1261
|
-
if (!lineId) return;
|
|
1262
|
-
if (!initialized) {
|
|
1263
|
-
setLoading(true);
|
|
1264
|
-
}
|
|
1265
|
-
setError(null);
|
|
1266
|
-
try {
|
|
1267
|
-
console.log("Fetching workspace metrics with params:", {
|
|
1268
|
-
lineId,
|
|
1269
|
-
queryDate,
|
|
1270
|
-
queryShiftId,
|
|
1271
|
-
metricsTable
|
|
1272
|
-
});
|
|
1273
|
-
const { data, error: fetchError } = await supabase.from(metricsTable).select(`
|
|
1274
|
-
workspace_name,
|
|
1275
|
-
total_output,
|
|
1276
|
-
avg_pph,
|
|
1277
|
-
efficiency,
|
|
1278
|
-
workspace_id,
|
|
1279
|
-
avg_cycle_time,
|
|
1280
|
-
performance_score,
|
|
1281
|
-
trend_score,
|
|
1282
|
-
line_id,
|
|
1283
|
-
total_day_output
|
|
1284
|
-
`).eq("date", queryDate).eq("shift_id", queryShiftId).eq("line_id", lineId).order("workspace_name", { ascending: true });
|
|
1285
|
-
if (fetchError) throw fetchError;
|
|
1286
|
-
const transformedData = (data || []).map((item) => ({
|
|
1287
|
-
company_id: entityConfig.companyId || "unknown",
|
|
1288
|
-
line_id: item.line_id,
|
|
1289
|
-
shift_id: queryShiftId,
|
|
1290
|
-
date: queryDate,
|
|
1291
|
-
workspace_uuid: item.workspace_id,
|
|
1292
|
-
workspace_name: item.workspace_name,
|
|
1293
|
-
action_count: item.total_output || 0,
|
|
1294
|
-
pph: item.avg_pph || 0,
|
|
1295
|
-
performance_score: item.performance_score || 0,
|
|
1296
|
-
avg_cycle_time: item.avg_cycle_time || 0,
|
|
1297
|
-
trend: item.trend_score === 1 ? 2 : 0,
|
|
1298
|
-
predicted_output: 0,
|
|
1299
|
-
efficiency: item.efficiency || 0,
|
|
1300
|
-
action_threshold: item.total_day_output || 0
|
|
1301
|
-
}));
|
|
1302
|
-
setWorkspaces(transformedData);
|
|
1303
|
-
setInitialized(true);
|
|
1304
|
-
} catch (err) {
|
|
1305
|
-
console.error("Error fetching workspace metrics:", err);
|
|
1306
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
1307
|
-
} finally {
|
|
1308
|
-
setLoading(false);
|
|
1309
|
-
}
|
|
1310
|
-
}, [lineId, queryDate, queryShiftId, metricsTable, supabase, entityConfig.companyId]);
|
|
1311
|
-
React14.useEffect(() => {
|
|
1312
|
-
if (!initialized) {
|
|
1313
|
-
fetchWorkspaceMetrics();
|
|
655
|
+
const { data, error } = await query;
|
|
656
|
+
if (error) {
|
|
657
|
+
console.error("Error in getWorkspacesData:", error);
|
|
658
|
+
throw error;
|
|
1314
659
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
};
|
|
1334
|
-
const channel = setupSubscription();
|
|
1335
|
-
return () => {
|
|
1336
|
-
if (channel) {
|
|
1337
|
-
supabase.removeChannel(channel);
|
|
1338
|
-
}
|
|
1339
|
-
};
|
|
1340
|
-
}, [lineId, queryDate, queryShiftId, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema]);
|
|
1341
|
-
React14.useEffect(() => {
|
|
1342
|
-
setInitialized(false);
|
|
1343
|
-
}, [lineId, queryDate, queryShiftId]);
|
|
1344
|
-
const refreshWorkspaces = fetchWorkspaceMetrics;
|
|
1345
|
-
return React14.useMemo(
|
|
1346
|
-
() => ({ workspaces, loading, error, refreshWorkspaces }),
|
|
1347
|
-
[workspaces, loading, error, refreshWorkspaces]
|
|
1348
|
-
);
|
|
1349
|
-
};
|
|
1350
|
-
|
|
1351
|
-
// src/lib/services/dashboardService.ts
|
|
1352
|
-
var getTable = (dbConfig, tableName) => {
|
|
1353
|
-
const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
|
|
1354
|
-
const userValue = dbConfig?.tables?.[tableName];
|
|
1355
|
-
return userValue ?? defaults2[tableName];
|
|
1356
|
-
};
|
|
1357
|
-
var dashboardService = {
|
|
1358
|
-
// Example for getLineInfo:
|
|
1359
|
-
async getLineInfo(lineIdInput) {
|
|
660
|
+
return (data || []).map((item) => ({
|
|
661
|
+
company_id: item.company_id,
|
|
662
|
+
line_id: item.line_id,
|
|
663
|
+
shift_id: item.shift_id,
|
|
664
|
+
date: item.date,
|
|
665
|
+
workspace_uuid: item.workspace_id,
|
|
666
|
+
workspace_name: item.workspace_name,
|
|
667
|
+
action_count: item.total_output || 0,
|
|
668
|
+
pph: item.avg_pph || 0,
|
|
669
|
+
performance_score: item.performance_score || 0,
|
|
670
|
+
avg_cycle_time: item.avg_cycle_time || 0,
|
|
671
|
+
trend: item.trend_score === 1 ? 2 : item.trend_score === 0 ? 0 : 1,
|
|
672
|
+
predicted_output: item.ideal_output || 0,
|
|
673
|
+
efficiency: item.efficiency || 0,
|
|
674
|
+
action_threshold: item.total_day_output || 0
|
|
675
|
+
}));
|
|
676
|
+
},
|
|
677
|
+
async getWorkspaceDetailedMetrics(workspaceUuid, dateProp, shiftIdProp) {
|
|
1360
678
|
const supabase = _getSupabaseInstance();
|
|
1361
679
|
const config = _getDashboardConfigInstance();
|
|
1362
|
-
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
1363
680
|
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
1364
681
|
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
1365
682
|
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
1366
683
|
const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
|
|
1367
|
-
const linesTable = getTable(dbConfig, "lines");
|
|
1368
|
-
const lineMetricsTable = getTable(dbConfig, "lineMetrics");
|
|
1369
684
|
const companyId = entityConfig.companyId;
|
|
685
|
+
if (!companyId) {
|
|
686
|
+
throw new Error("Company ID must be configured for detailed workspace metrics.");
|
|
687
|
+
}
|
|
1370
688
|
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
1371
|
-
const metricsTable = `${metricsTablePrefixStr}_${companyId
|
|
1372
|
-
const defaultLineId = entityConfig.defaultLineId;
|
|
1373
|
-
const secondaryLineId = entityConfig.secondaryLineId;
|
|
1374
|
-
const factoryViewId = entityConfig.factoryViewId ?? "factory";
|
|
1375
|
-
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
1376
|
-
const { shiftId, date } = getCurrentShift(defaultTimezone, shiftConfig);
|
|
1377
|
-
const lineId = lineIdInput;
|
|
1378
|
-
if (lineId === factoryViewId) {
|
|
1379
|
-
if (!defaultLineId || !secondaryLineId || !companyId) {
|
|
1380
|
-
throw new Error("Factory View requires defaultLineId, secondaryLineId, and companyId to be configured.");
|
|
1381
|
-
}
|
|
1382
|
-
const { data: lineData2, error: lineError2 } = await supabase.from(linesTable).select(`
|
|
1383
|
-
id,
|
|
1384
|
-
line_name,
|
|
1385
|
-
factory_id,
|
|
1386
|
-
factories!lines_factory_id_fkey(factory_name),
|
|
1387
|
-
company_id,
|
|
1388
|
-
companies!lines_company_id_fkey(company_name:name)
|
|
1389
|
-
`).eq("id", defaultLineId).maybeSingle();
|
|
1390
|
-
if (lineError2) throw lineError2;
|
|
1391
|
-
if (!lineData2) throw new Error(`Configured default line (${defaultLineId}) not found`);
|
|
1392
|
-
const { data: metricsData, error: metricsError } = await supabase.from(lineMetricsTable).select("*").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
|
|
1393
|
-
if (metricsError) throw metricsError;
|
|
1394
|
-
const { data: performanceData2, error: performanceError2 } = await supabase.from(metricsTable).select("efficiency").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
|
|
1395
|
-
if (performanceError2) throw performanceError2;
|
|
1396
|
-
const underperformingCount2 = performanceData2?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
|
|
1397
|
-
const totalValidWorkspaces2 = performanceData2?.filter((w) => w.efficiency >= 10).length || 0;
|
|
1398
|
-
const combinedMetrics = (metricsData || []).reduce((acc, m) => {
|
|
1399
|
-
acc.avg_efficiency += m.efficiency ?? 0;
|
|
1400
|
-
acc.current_output += m.current_output ?? 0;
|
|
1401
|
-
acc.ideal_output += m.ideal_output ?? m.line_threshold ?? 0;
|
|
1402
|
-
return acc;
|
|
1403
|
-
}, { avg_efficiency: 0, current_output: 0, ideal_output: 0 });
|
|
1404
|
-
metricsData?.length || 1;
|
|
1405
|
-
return {
|
|
1406
|
-
line_id: factoryViewId,
|
|
1407
|
-
// Use configured factory view ID
|
|
1408
|
-
line_name: "Factory View",
|
|
1409
|
-
// Consider making this configurable?
|
|
1410
|
-
company_id: lineData2.company_id,
|
|
1411
|
-
company_name: lineData2.companies?.[0]?.company_name ?? "",
|
|
1412
|
-
factory_id: lineData2.factory_id,
|
|
1413
|
-
factory_name: lineData2.factories?.[0]?.factory_name ?? "",
|
|
1414
|
-
shift_id: shiftId,
|
|
1415
|
-
date,
|
|
1416
|
-
metrics: {
|
|
1417
|
-
avg_efficiency: (performanceData2?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces2 || 1),
|
|
1418
|
-
// Use performance data for avg efficiency
|
|
1419
|
-
avg_cycle_time: 0,
|
|
1420
|
-
// Needs calculation logic if required for factory view
|
|
1421
|
-
current_output: combinedMetrics.current_output,
|
|
1422
|
-
ideal_output: combinedMetrics.ideal_output,
|
|
1423
|
-
total_workspaces: 44,
|
|
1424
|
-
// SRC ALIGNMENT: Use hardcoded 44 for factory view total_workspaces
|
|
1425
|
-
underperforming_workspaces: underperformingCount2,
|
|
1426
|
-
underperforming_workspace_names: [],
|
|
1427
|
-
// Populate if needed
|
|
1428
|
-
underperforming_workspace_uuids: [],
|
|
1429
|
-
// Populate if needed
|
|
1430
|
-
output_array: [],
|
|
1431
|
-
// Combine if needed
|
|
1432
|
-
line_threshold: combinedMetrics.ideal_output,
|
|
1433
|
-
threshold_pph: 0,
|
|
1434
|
-
// Needs calculation logic if required
|
|
1435
|
-
shift_start: shiftConfig.dayShift?.startTime || "06:00",
|
|
1436
|
-
// Use config
|
|
1437
|
-
shift_end: shiftConfig.dayShift?.endTime || "18:00",
|
|
1438
|
-
// Use config
|
|
1439
|
-
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1440
|
-
poorest_performing_workspaces: []
|
|
1441
|
-
// Populate if needed
|
|
1442
|
-
}
|
|
1443
|
-
};
|
|
1444
|
-
}
|
|
1445
|
-
if (!companyId) {
|
|
1446
|
-
throw new Error("Company ID must be configured for individual line requests.");
|
|
1447
|
-
}
|
|
1448
|
-
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();
|
|
1449
|
-
if (lineError) throw lineError;
|
|
1450
|
-
if (!lineData) throw new Error(`Line with ID ${lineId} not found`);
|
|
1451
|
-
let metricsFromDb = null;
|
|
1452
|
-
try {
|
|
1453
|
-
const { data: fetchedMetrics, error } = await supabase.from(lineMetricsTable).select("*").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date).maybeSingle();
|
|
1454
|
-
if (error) throw error;
|
|
1455
|
-
metricsFromDb = fetchedMetrics;
|
|
1456
|
-
} catch (err) {
|
|
1457
|
-
console.error(`Error fetching line metrics for ${lineId}:`, err);
|
|
1458
|
-
}
|
|
1459
|
-
const { data: performanceData, error: performanceError } = await supabase.from(metricsTable).select("efficiency").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date);
|
|
1460
|
-
if (performanceError) throw performanceError;
|
|
1461
|
-
const underperformingCount = performanceData?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
|
|
1462
|
-
const totalValidWorkspaces = performanceData?.filter((w) => w.efficiency >= 10).length || 0;
|
|
1463
|
-
const avgEfficiencyFromPerf = (performanceData?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces || 1);
|
|
1464
|
-
const useFallbackMetrics = !metricsFromDb;
|
|
1465
|
-
let metricsForReturn;
|
|
1466
|
-
if (useFallbackMetrics) {
|
|
1467
|
-
metricsForReturn = {
|
|
1468
|
-
avg_efficiency: 0,
|
|
1469
|
-
avg_cycle_time: 0,
|
|
1470
|
-
current_output: 0,
|
|
1471
|
-
ideal_output: 0,
|
|
1472
|
-
total_workspaces: 42,
|
|
1473
|
-
underperforming_workspaces: underperformingCount,
|
|
1474
|
-
underperforming_workspace_names: [],
|
|
1475
|
-
underperforming_workspace_uuids: [],
|
|
1476
|
-
output_array: [],
|
|
1477
|
-
line_threshold: 0,
|
|
1478
|
-
threshold_pph: 0,
|
|
1479
|
-
shift_start: "06:00",
|
|
1480
|
-
shift_end: "14:00",
|
|
1481
|
-
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1482
|
-
poorest_performing_workspaces: []
|
|
1483
|
-
};
|
|
1484
|
-
} else {
|
|
1485
|
-
metricsForReturn = {
|
|
1486
|
-
avg_efficiency: metricsFromDb.efficiency ?? avgEfficiencyFromPerf,
|
|
1487
|
-
avg_cycle_time: metricsFromDb.avg_cycle_time || 0,
|
|
1488
|
-
current_output: metricsFromDb.current_output || 0,
|
|
1489
|
-
ideal_output: metricsFromDb.ideal_output || metricsFromDb.line_threshold || 0,
|
|
1490
|
-
total_workspaces: metricsFromDb.total_workspaces ?? workspaceConfig.totalWorkspaces ?? 42,
|
|
1491
|
-
underperforming_workspaces: underperformingCount,
|
|
1492
|
-
underperforming_workspace_names: metricsFromDb.underperforming_workspace_names || [],
|
|
1493
|
-
underperforming_workspace_uuids: metricsFromDb.underperforming_workspace_uuids || [],
|
|
1494
|
-
output_array: metricsFromDb.output_array || [],
|
|
1495
|
-
line_threshold: metricsFromDb.line_threshold || 0,
|
|
1496
|
-
threshold_pph: metricsFromDb.threshold_pph || 0,
|
|
1497
|
-
shift_start: metricsFromDb.shift_start || shiftConfig.dayShift?.startTime || "06:00",
|
|
1498
|
-
shift_end: metricsFromDb.shift_end || shiftConfig.dayShift?.endTime || "18:00",
|
|
1499
|
-
last_updated: metricsFromDb.last_updated || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1500
|
-
poorest_performing_workspaces: metricsFromDb.poorest_performing_workspaces || []
|
|
1501
|
-
};
|
|
1502
|
-
if (metricsFromDb.efficiency === null || metricsFromDb.efficiency === void 0) {
|
|
1503
|
-
metricsForReturn.avg_efficiency = avgEfficiencyFromPerf;
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
return {
|
|
1507
|
-
line_id: lineData.id,
|
|
1508
|
-
line_name: lineData.line_name,
|
|
1509
|
-
company_id: lineData.company_id,
|
|
1510
|
-
company_name: lineData.companies?.[0]?.company_name ?? "",
|
|
1511
|
-
factory_id: lineData.factory_id,
|
|
1512
|
-
factory_name: lineData.factories?.[0]?.factory_name ?? "",
|
|
1513
|
-
shift_id: shiftId,
|
|
1514
|
-
date,
|
|
1515
|
-
metrics: metricsForReturn
|
|
1516
|
-
};
|
|
1517
|
-
},
|
|
1518
|
-
async getWorkspacesData(lineIdInput, dateProp, shiftProp) {
|
|
1519
|
-
const supabase = _getSupabaseInstance();
|
|
1520
|
-
const config = _getDashboardConfigInstance();
|
|
1521
|
-
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
1522
|
-
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
1523
|
-
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
1524
|
-
const companyId = entityConfig.companyId;
|
|
1525
|
-
if (!companyId) {
|
|
1526
|
-
throw new Error("Company ID must be configured for workspace data requests.");
|
|
1527
|
-
}
|
|
1528
|
-
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
1529
|
-
const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
|
|
1530
|
-
const defaultLineId = entityConfig.defaultLineId;
|
|
1531
|
-
const secondaryLineId = entityConfig.secondaryLineId;
|
|
1532
|
-
const factoryViewId = entityConfig.factoryViewId ?? "factory";
|
|
1533
|
-
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
1534
|
-
const currentShiftResult = getCurrentShift(defaultTimezone, shiftConfig);
|
|
1535
|
-
const queryDate = dateProp || getOperationalDate(defaultTimezone);
|
|
1536
|
-
const queryShiftId = shiftProp ?? currentShiftResult.shiftId;
|
|
1537
|
-
const lineId = lineIdInput;
|
|
1538
|
-
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);
|
|
1539
|
-
if (!lineId || lineId === factoryViewId) {
|
|
1540
|
-
if (!defaultLineId || !secondaryLineId) {
|
|
1541
|
-
throw new Error("Factory View requires defaultLineId and secondaryLineId to be configured for workspace data.");
|
|
1542
|
-
}
|
|
1543
|
-
query = query.in("line_id", [defaultLineId, secondaryLineId]);
|
|
1544
|
-
} else {
|
|
1545
|
-
query = query.eq("line_id", lineId);
|
|
1546
|
-
}
|
|
1547
|
-
const { data, error } = await query;
|
|
1548
|
-
if (error) {
|
|
1549
|
-
console.error("Error in getWorkspacesData:", error);
|
|
1550
|
-
throw error;
|
|
1551
|
-
}
|
|
1552
|
-
return (data || []).map((item) => ({
|
|
1553
|
-
company_id: item.company_id,
|
|
1554
|
-
line_id: item.line_id,
|
|
1555
|
-
shift_id: item.shift_id,
|
|
1556
|
-
date: item.date,
|
|
1557
|
-
workspace_uuid: item.workspace_id,
|
|
1558
|
-
workspace_name: item.workspace_name,
|
|
1559
|
-
action_count: item.total_output || 0,
|
|
1560
|
-
pph: item.avg_pph || 0,
|
|
1561
|
-
performance_score: item.performance_score || 0,
|
|
1562
|
-
avg_cycle_time: item.avg_cycle_time || 0,
|
|
1563
|
-
trend: item.trend_score === 1 ? 2 : item.trend_score === 0 ? 0 : 1,
|
|
1564
|
-
predicted_output: item.ideal_output || 0,
|
|
1565
|
-
efficiency: item.efficiency || 0,
|
|
1566
|
-
action_threshold: item.total_day_output || 0
|
|
1567
|
-
}));
|
|
1568
|
-
},
|
|
1569
|
-
async getWorkspaceDetailedMetrics(workspaceUuid, dateProp, shiftIdProp) {
|
|
1570
|
-
const supabase = _getSupabaseInstance();
|
|
1571
|
-
const config = _getDashboardConfigInstance();
|
|
1572
|
-
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
1573
|
-
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
1574
|
-
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
1575
|
-
const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
|
|
1576
|
-
const companyId = entityConfig.companyId;
|
|
1577
|
-
if (!companyId) {
|
|
1578
|
-
throw new Error("Company ID must be configured for detailed workspace metrics.");
|
|
1579
|
-
}
|
|
1580
|
-
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
1581
|
-
const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
|
|
689
|
+
const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
|
|
1582
690
|
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
1583
691
|
const currentShiftResult = getCurrentShift(defaultTimezone, shiftConfig);
|
|
1584
692
|
const queryDate = dateProp || getOperationalDate(defaultTimezone);
|
|
@@ -1718,7 +826,7 @@ var dashboardService = {
|
|
|
1718
826
|
const supabase = _getSupabaseInstance();
|
|
1719
827
|
const config = _getDashboardConfigInstance();
|
|
1720
828
|
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
1721
|
-
const linesTable =
|
|
829
|
+
const linesTable = getTable2(dbConfig, "lines");
|
|
1722
830
|
const companyId = config.entityConfig?.companyId;
|
|
1723
831
|
try {
|
|
1724
832
|
let query = supabase.from(linesTable).select(`
|
|
@@ -1760,8 +868,8 @@ var dashboardService = {
|
|
|
1760
868
|
const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
|
|
1761
869
|
const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
|
|
1762
870
|
const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
|
|
1763
|
-
const linesTable =
|
|
1764
|
-
const lineMetricsTable =
|
|
871
|
+
const linesTable = getTable2(dbConfig, "lines");
|
|
872
|
+
const lineMetricsTable = getTable2(dbConfig, "lineMetrics");
|
|
1765
873
|
const companyId = entityConfig.companyId;
|
|
1766
874
|
const metricsTablePrefixStr = getMetricsTablePrefix();
|
|
1767
875
|
const metricsTable = `${metricsTablePrefixStr}_${companyId ? companyId.replace(/-/g, "_") : "unknown_company"}`;
|
|
@@ -1912,7 +1020,7 @@ var dashboardService = {
|
|
|
1912
1020
|
const formattedStartDate = formatDate(startDate);
|
|
1913
1021
|
const formattedEndDate = formatDate(endDate);
|
|
1914
1022
|
try {
|
|
1915
|
-
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 });
|
|
1023
|
+
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 });
|
|
1916
1024
|
if (error) throw error;
|
|
1917
1025
|
if (!data) return [];
|
|
1918
1026
|
const transformedData = data.map((item) => ({
|
|
@@ -1925,7 +1033,8 @@ var dashboardService = {
|
|
|
1925
1033
|
ideal_output: item.ideal_output || 0,
|
|
1926
1034
|
avg_pph: item.avg_pph || 0,
|
|
1927
1035
|
pph_threshold: item.pph_threshold || 0,
|
|
1928
|
-
workspace_rank: item.workspace_rank || 0
|
|
1036
|
+
workspace_rank: item.workspace_rank || 0,
|
|
1037
|
+
idle_time: item.idle_time || 0
|
|
1929
1038
|
}));
|
|
1930
1039
|
return transformedData;
|
|
1931
1040
|
} catch (err) {
|
|
@@ -1938,7 +1047,7 @@ var dashboardService = {
|
|
|
1938
1047
|
const config = _getDashboardConfigInstance();
|
|
1939
1048
|
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
1940
1049
|
const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
|
|
1941
|
-
const lineMetricsTable =
|
|
1050
|
+
const lineMetricsTable = getTable2(dbConfig, "lineMetrics");
|
|
1942
1051
|
const defaultLineId = entityConfig.defaultLineId;
|
|
1943
1052
|
const secondaryLineId = entityConfig.secondaryLineId;
|
|
1944
1053
|
const factoryViewId = entityConfig.factoryViewId ?? "factory";
|
|
@@ -2025,162 +1134,35 @@ var dashboardService = {
|
|
|
2025
1134
|
}
|
|
2026
1135
|
};
|
|
2027
1136
|
|
|
2028
|
-
// src/lib/
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
...fetchedData,
|
|
2058
|
-
hourly_action_counts: initialHourlyCounts
|
|
2059
|
-
});
|
|
2060
|
-
setIsLoading(false);
|
|
2061
|
-
if (fetchedData.hourly_action_counts && fetchedData.hourly_action_counts.length > 0) {
|
|
2062
|
-
const totalSteps = 60;
|
|
2063
|
-
let currentStep = 0;
|
|
2064
|
-
let animationIntervalId = setInterval(() => {
|
|
2065
|
-
currentStep++;
|
|
2066
|
-
const progress6 = currentStep / totalSteps;
|
|
2067
|
-
setMetrics((prevMetrics) => {
|
|
2068
|
-
const currentHourlyData = (fetchedData.hourly_action_counts || []).map(
|
|
2069
|
-
(target) => Math.round(target * progress6)
|
|
2070
|
-
);
|
|
2071
|
-
return {
|
|
2072
|
-
...fetchedData,
|
|
2073
|
-
// Base with all other correct data from the latest fetch
|
|
2074
|
-
hourly_action_counts: currentHourlyData
|
|
2075
|
-
};
|
|
2076
|
-
});
|
|
2077
|
-
if (currentStep >= totalSteps) {
|
|
2078
|
-
if (animationIntervalId) clearInterval(animationIntervalId);
|
|
2079
|
-
setMetrics(fetchedData);
|
|
2080
|
-
}
|
|
2081
|
-
}, 1e3 / 60);
|
|
2082
|
-
} else {
|
|
2083
|
-
setMetrics(fetchedData);
|
|
2084
|
-
}
|
|
2085
|
-
} catch (err) {
|
|
2086
|
-
console.error("Error fetching historic workspace metrics:", err);
|
|
2087
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
2088
|
-
setIsLoading(false);
|
|
2089
|
-
setMetrics(null);
|
|
2090
|
-
}
|
|
2091
|
-
}, [workspaceId, date, inputShiftId]);
|
|
2092
|
-
React14.useEffect(() => {
|
|
2093
|
-
fetchAndAnimateMetrics();
|
|
2094
|
-
}, [fetchAndAnimateMetrics]);
|
|
2095
|
-
const refetch = React14.useCallback(async () => {
|
|
2096
|
-
if (!workspaceId || !date || inputShiftId === void 0) {
|
|
2097
|
-
setError(null);
|
|
2098
|
-
return;
|
|
2099
|
-
}
|
|
2100
|
-
setIsLoading(true);
|
|
2101
|
-
setError(null);
|
|
2102
|
-
try {
|
|
2103
|
-
const fetchedData = await dashboardService.getWorkspaceDetailedMetrics(
|
|
2104
|
-
workspaceId,
|
|
2105
|
-
date,
|
|
2106
|
-
inputShiftId
|
|
2107
|
-
);
|
|
2108
|
-
if (!fetchedData) {
|
|
2109
|
-
setMetrics(null);
|
|
2110
|
-
return;
|
|
2111
|
-
}
|
|
2112
|
-
setMetrics(fetchedData);
|
|
2113
|
-
} catch (err) {
|
|
2114
|
-
console.error("Error re-fetching historic workspace metrics:", err);
|
|
2115
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
2116
|
-
setMetrics(null);
|
|
2117
|
-
} finally {
|
|
2118
|
-
setIsLoading(false);
|
|
2119
|
-
}
|
|
2120
|
-
}, [workspaceId, date, inputShiftId]);
|
|
2121
|
-
return {
|
|
2122
|
-
metrics: metrics2,
|
|
2123
|
-
isLoading,
|
|
2124
|
-
error,
|
|
2125
|
-
refetch
|
|
2126
|
-
};
|
|
2127
|
-
};
|
|
2128
|
-
|
|
2129
|
-
// src/lib/services/actionService.ts
|
|
2130
|
-
var getTable2 = (dbConfig, tableName) => {
|
|
2131
|
-
const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
|
|
2132
|
-
const userValue = dbConfig?.tables?.[tableName];
|
|
2133
|
-
return userValue ?? defaults2[tableName];
|
|
2134
|
-
};
|
|
2135
|
-
var actionService = {
|
|
2136
|
-
async getActionsByName(actionNames, companyIdInput) {
|
|
2137
|
-
const supabase = _getSupabaseInstance();
|
|
2138
|
-
const config = _getDashboardConfigInstance();
|
|
2139
|
-
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
2140
|
-
const entityConfig = config.entityConfig;
|
|
2141
|
-
const actionsTable = getTable2(dbConfig, "actions");
|
|
2142
|
-
const targetCompanyId = companyIdInput ?? entityConfig?.companyId;
|
|
2143
|
-
if (!targetCompanyId) {
|
|
2144
|
-
throw new Error("Company ID must be provided either via entityConfig.companyId or as an argument to getActionsByName.");
|
|
2145
|
-
}
|
|
2146
|
-
const { data, error } = await supabase.from(actionsTable).select("id, action_name, company_id").eq("company_id", targetCompanyId).in("action_name", actionNames);
|
|
2147
|
-
if (error) {
|
|
2148
|
-
console.error(`Error fetching actions from table ${actionsTable}:`, error);
|
|
2149
|
-
throw error;
|
|
2150
|
-
}
|
|
2151
|
-
return data || [];
|
|
2152
|
-
}
|
|
2153
|
-
};
|
|
2154
|
-
|
|
2155
|
-
// src/lib/services/realtimeService.ts
|
|
2156
|
-
function isValidLineInfoPayload(payload) {
|
|
2157
|
-
return payload && typeof payload === "object" && "line_id" in payload;
|
|
2158
|
-
}
|
|
2159
|
-
function isValidWorkspaceMetricsPayload(payload) {
|
|
2160
|
-
return payload && typeof payload === "object" && "workspace_uuid" in payload;
|
|
2161
|
-
}
|
|
2162
|
-
function isValidWorkspaceDetailedMetricsPayload(payload) {
|
|
2163
|
-
return payload && typeof payload === "object" && "workspace_id" in payload && "hourly_action_counts" in payload;
|
|
2164
|
-
}
|
|
2165
|
-
var realtimeService = {
|
|
2166
|
-
subscribeToLineInfo(lineId, shiftId, date, onDataUpdate) {
|
|
2167
|
-
const supabase = _getSupabaseInstance();
|
|
2168
|
-
const config = _getDashboardConfigInstance();
|
|
2169
|
-
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
2170
|
-
const schema = dbConfig.schema ?? "public";
|
|
2171
|
-
const channelName = `line_info_${lineId}_${shiftId}_${date}`;
|
|
2172
|
-
const TABLE_NAME = "line_info";
|
|
2173
|
-
const channel = supabase.channel(channelName);
|
|
2174
|
-
channel.on(
|
|
2175
|
-
"postgres_changes",
|
|
2176
|
-
{ event: "*", schema, table: TABLE_NAME, filter: `line_id=eq.${lineId}` },
|
|
2177
|
-
(payload) => {
|
|
2178
|
-
const record = payload.new ?? payload.old;
|
|
2179
|
-
if (record && isValidLineInfoPayload(record)) {
|
|
2180
|
-
if ((!record.shift_id || record.shift_id === shiftId) && (!record.date || record.date === date)) {
|
|
2181
|
-
onDataUpdate(record);
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
1137
|
+
// src/lib/services/realtimeService.ts
|
|
1138
|
+
function isValidLineInfoPayload(payload) {
|
|
1139
|
+
return payload && typeof payload === "object" && "line_id" in payload;
|
|
1140
|
+
}
|
|
1141
|
+
function isValidWorkspaceMetricsPayload(payload) {
|
|
1142
|
+
return payload && typeof payload === "object" && "workspace_uuid" in payload;
|
|
1143
|
+
}
|
|
1144
|
+
function isValidWorkspaceDetailedMetricsPayload(payload) {
|
|
1145
|
+
return payload && typeof payload === "object" && "workspace_id" in payload && "hourly_action_counts" in payload;
|
|
1146
|
+
}
|
|
1147
|
+
var realtimeService = {
|
|
1148
|
+
subscribeToLineInfo(lineId, shiftId, date, onDataUpdate) {
|
|
1149
|
+
const supabase = _getSupabaseInstance();
|
|
1150
|
+
const config = _getDashboardConfigInstance();
|
|
1151
|
+
const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
|
|
1152
|
+
const schema = dbConfig.schema ?? "public";
|
|
1153
|
+
const channelName = `line_info_${lineId}_${shiftId}_${date}`;
|
|
1154
|
+
const TABLE_NAME = "line_info";
|
|
1155
|
+
const channel = supabase.channel(channelName);
|
|
1156
|
+
channel.on(
|
|
1157
|
+
"postgres_changes",
|
|
1158
|
+
{ event: "*", schema, table: TABLE_NAME, filter: `line_id=eq.${lineId}` },
|
|
1159
|
+
(payload) => {
|
|
1160
|
+
const record = payload.new ?? payload.old;
|
|
1161
|
+
if (record && isValidLineInfoPayload(record)) {
|
|
1162
|
+
if ((!record.shift_id || record.shift_id === shiftId) && (!record.date || record.date === date)) {
|
|
1163
|
+
onDataUpdate(record);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
2184
1166
|
}
|
|
2185
1167
|
).subscribe((status, err) => {
|
|
2186
1168
|
if (err) console.error(`[RealtimeService] Line info subscription error for ${channelName}:`, err);
|
|
@@ -2853,6 +1835,7 @@ var authRateLimitService = {
|
|
|
2853
1835
|
clearAllRateLimits
|
|
2854
1836
|
};
|
|
2855
1837
|
var isMixpanelInitialized = false;
|
|
1838
|
+
var currentUserProperties;
|
|
2856
1839
|
var initializeCoreMixpanel = (token, debug, trackPageView) => {
|
|
2857
1840
|
if (!token) {
|
|
2858
1841
|
console.warn("Mixpanel token not provided for initialization. Mixpanel will not be enabled.");
|
|
@@ -2878,7 +1861,13 @@ var trackCorePageView = (pageName, properties) => {
|
|
|
2878
1861
|
};
|
|
2879
1862
|
var trackCoreEvent = (eventName, properties) => {
|
|
2880
1863
|
if (!isMixpanelInitialized) return;
|
|
2881
|
-
|
|
1864
|
+
const mergedProps = {
|
|
1865
|
+
// Precedence order: explicit properties passed by caller should override
|
|
1866
|
+
// automatically appended user properties to avoid accidental overwrites.
|
|
1867
|
+
...currentUserProperties || {},
|
|
1868
|
+
...properties || {}
|
|
1869
|
+
};
|
|
1870
|
+
mixpanel__default.default.track(eventName, mergedProps);
|
|
2882
1871
|
};
|
|
2883
1872
|
var identifyCoreUser = (userId, userProperties) => {
|
|
2884
1873
|
if (!isMixpanelInitialized) return;
|
|
@@ -2886,6 +1875,7 @@ var identifyCoreUser = (userId, userProperties) => {
|
|
|
2886
1875
|
if (userProperties) {
|
|
2887
1876
|
mixpanel__default.default.people.set(userProperties);
|
|
2888
1877
|
}
|
|
1878
|
+
currentUserProperties = { ...userProperties };
|
|
2889
1879
|
};
|
|
2890
1880
|
var resetCoreMixpanel = () => {
|
|
2891
1881
|
if (!isMixpanelInitialized) return;
|
|
@@ -2908,195 +1898,1239 @@ var SSEChatClient = class {
|
|
|
2908
1898
|
}
|
|
2909
1899
|
}
|
|
2910
1900
|
}
|
|
2911
|
-
const controller = new AbortController();
|
|
2912
|
-
this.controllers.set(connectionId, controller);
|
|
2913
|
-
console.log("[SSEClient] Sending message:", {
|
|
2914
|
-
message,
|
|
2915
|
-
thread_id: threadId,
|
|
2916
|
-
user_id: userId,
|
|
2917
|
-
context
|
|
1901
|
+
const controller = new AbortController();
|
|
1902
|
+
this.controllers.set(connectionId, controller);
|
|
1903
|
+
console.log("[SSEClient] Sending message:", {
|
|
1904
|
+
message,
|
|
1905
|
+
thread_id: threadId,
|
|
1906
|
+
user_id: userId,
|
|
1907
|
+
context
|
|
1908
|
+
});
|
|
1909
|
+
const agnoApiUrl = this.baseUrl || "https://optifye-agent-production.up.railway.app";
|
|
1910
|
+
const endpoint = `${agnoApiUrl}/api/chat`;
|
|
1911
|
+
console.log("[SSEClient] Posting directly to AGNO:", endpoint);
|
|
1912
|
+
const response = await fetch(endpoint, {
|
|
1913
|
+
method: "POST",
|
|
1914
|
+
headers: {
|
|
1915
|
+
"Content-Type": "application/json",
|
|
1916
|
+
"Accept": "text/event-stream"
|
|
1917
|
+
},
|
|
1918
|
+
body: JSON.stringify({
|
|
1919
|
+
message,
|
|
1920
|
+
thread_id: threadId,
|
|
1921
|
+
user_id: userId,
|
|
1922
|
+
company_id: context.companyId,
|
|
1923
|
+
line_id: context.lineId,
|
|
1924
|
+
shift_id: context.shiftId
|
|
1925
|
+
}),
|
|
1926
|
+
signal: controller.signal,
|
|
1927
|
+
// Don't include credentials since the API returns Access-Control-Allow-Origin: *
|
|
1928
|
+
// credentials: 'include', // Include cookies for CORS
|
|
1929
|
+
mode: "cors"
|
|
1930
|
+
// Explicitly set CORS mode
|
|
1931
|
+
});
|
|
1932
|
+
console.log("[SSEClient] Response status:", response.status);
|
|
1933
|
+
console.log("[SSEClient] Response headers:", Object.fromEntries(response.headers.entries()));
|
|
1934
|
+
if (!response.ok) {
|
|
1935
|
+
let errorMessage = `Chat request failed with status ${response.status}`;
|
|
1936
|
+
try {
|
|
1937
|
+
const error = await response.json();
|
|
1938
|
+
errorMessage = error.detail || error.message || errorMessage;
|
|
1939
|
+
} catch (e) {
|
|
1940
|
+
try {
|
|
1941
|
+
const text = await response.text();
|
|
1942
|
+
if (text) errorMessage = text;
|
|
1943
|
+
} catch (textError) {
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
console.error("[SSEClient] Error response:", errorMessage);
|
|
1947
|
+
throw new Error(errorMessage);
|
|
1948
|
+
}
|
|
1949
|
+
const contentType = response.headers.get("content-type");
|
|
1950
|
+
if (!contentType?.includes("text/event-stream")) {
|
|
1951
|
+
console.warn("[SSEClient] Unexpected content-type:", contentType);
|
|
1952
|
+
}
|
|
1953
|
+
try {
|
|
1954
|
+
await this.handleSSEStream(response, callbacks);
|
|
1955
|
+
} finally {
|
|
1956
|
+
this.controllers.delete(connectionId);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
async handleSSEStream(response, callbacks) {
|
|
1960
|
+
if (!response.body) {
|
|
1961
|
+
console.error("[SSEClient] Response body is null");
|
|
1962
|
+
throw new Error("No response body available for streaming");
|
|
1963
|
+
}
|
|
1964
|
+
const reader = response.body.getReader();
|
|
1965
|
+
const decoder = new TextDecoder();
|
|
1966
|
+
let buffer = "";
|
|
1967
|
+
try {
|
|
1968
|
+
console.log("[SSEClient] Starting to read stream...");
|
|
1969
|
+
while (true) {
|
|
1970
|
+
const { done, value } = await reader.read();
|
|
1971
|
+
if (done) {
|
|
1972
|
+
console.log("[SSEClient] Stream ended");
|
|
1973
|
+
break;
|
|
1974
|
+
}
|
|
1975
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1976
|
+
const lines = buffer.split("\n");
|
|
1977
|
+
buffer = lines.pop() || "";
|
|
1978
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1979
|
+
const line = lines[i].trim();
|
|
1980
|
+
if (!line) continue;
|
|
1981
|
+
console.log("[SSEClient] Processing line:", line);
|
|
1982
|
+
if (line.startsWith("event:")) {
|
|
1983
|
+
const event = line.slice(6).trim();
|
|
1984
|
+
console.log("[SSEClient] Event type:", event);
|
|
1985
|
+
const nextLine = lines[i + 1];
|
|
1986
|
+
if (nextLine?.startsWith("data:")) {
|
|
1987
|
+
const dataStr = nextLine.slice(5).trim();
|
|
1988
|
+
console.log("[SSEClient] Event data:", dataStr);
|
|
1989
|
+
try {
|
|
1990
|
+
const data = JSON.parse(dataStr);
|
|
1991
|
+
switch (event) {
|
|
1992
|
+
case "thread":
|
|
1993
|
+
callbacks.onThreadCreated?.(data.thread_id);
|
|
1994
|
+
break;
|
|
1995
|
+
case "message":
|
|
1996
|
+
callbacks.onMessage?.(data.text);
|
|
1997
|
+
break;
|
|
1998
|
+
case "reasoning":
|
|
1999
|
+
callbacks.onReasoning?.(data.text);
|
|
2000
|
+
break;
|
|
2001
|
+
case "complete":
|
|
2002
|
+
callbacks.onComplete?.(data.message_id);
|
|
2003
|
+
break;
|
|
2004
|
+
case "error":
|
|
2005
|
+
callbacks.onError?.(data.error);
|
|
2006
|
+
break;
|
|
2007
|
+
}
|
|
2008
|
+
} catch (e) {
|
|
2009
|
+
console.error("[SSEClient] Failed to parse data:", dataStr, e);
|
|
2010
|
+
}
|
|
2011
|
+
i++;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
} finally {
|
|
2017
|
+
reader.releaseLock();
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
abort(threadId) {
|
|
2021
|
+
if (threadId) {
|
|
2022
|
+
for (const [id3, controller] of this.controllers.entries()) {
|
|
2023
|
+
if (id3.startsWith(threadId)) {
|
|
2024
|
+
controller.abort();
|
|
2025
|
+
this.controllers.delete(id3);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
} else {
|
|
2029
|
+
for (const [id3, controller] of this.controllers.entries()) {
|
|
2030
|
+
controller.abort();
|
|
2031
|
+
}
|
|
2032
|
+
this.controllers.clear();
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
};
|
|
2036
|
+
|
|
2037
|
+
// src/lib/services/chatService.ts
|
|
2038
|
+
async function getUserThreads(userId, limit = 20) {
|
|
2039
|
+
const supabase = _getSupabaseInstance();
|
|
2040
|
+
const { data, error } = await supabase.schema("ai").from("chat_threads").select("*").eq("user_id", userId).order("updated_at", { ascending: false }).limit(limit);
|
|
2041
|
+
if (error) throw error;
|
|
2042
|
+
return data;
|
|
2043
|
+
}
|
|
2044
|
+
async function getUserThreadsPaginated(userId, page = 1, pageSize = 20) {
|
|
2045
|
+
const supabase = _getSupabaseInstance();
|
|
2046
|
+
const from = (page - 1) * pageSize;
|
|
2047
|
+
const to = from + pageSize - 1;
|
|
2048
|
+
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);
|
|
2049
|
+
if (error) throw error;
|
|
2050
|
+
return {
|
|
2051
|
+
threads: data,
|
|
2052
|
+
totalCount: count || 0,
|
|
2053
|
+
totalPages: Math.ceil((count || 0) / pageSize),
|
|
2054
|
+
currentPage: page
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
async function getThreadMessages(threadId, limit = 50, beforePosition) {
|
|
2058
|
+
const supabase = _getSupabaseInstance();
|
|
2059
|
+
let query = supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
|
|
2060
|
+
if (beforePosition !== void 0) {
|
|
2061
|
+
query = query.lt("position", beforePosition);
|
|
2062
|
+
}
|
|
2063
|
+
const { data, error } = await query.limit(limit);
|
|
2064
|
+
if (error) throw error;
|
|
2065
|
+
return data;
|
|
2066
|
+
}
|
|
2067
|
+
async function getAllThreadMessages(threadId) {
|
|
2068
|
+
const supabase = _getSupabaseInstance();
|
|
2069
|
+
const { data, error } = await supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
|
|
2070
|
+
if (error) throw error;
|
|
2071
|
+
return data;
|
|
2072
|
+
}
|
|
2073
|
+
async function updateThreadTitle(threadId, newTitle) {
|
|
2074
|
+
const supabase = _getSupabaseInstance();
|
|
2075
|
+
const { data, error } = await supabase.schema("ai").from("chat_threads").update({
|
|
2076
|
+
title: newTitle,
|
|
2077
|
+
auto_title: false,
|
|
2078
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2079
|
+
}).eq("id", threadId).select().single();
|
|
2080
|
+
if (error) throw error;
|
|
2081
|
+
return data;
|
|
2082
|
+
}
|
|
2083
|
+
async function deleteThread(threadId) {
|
|
2084
|
+
const supabase = _getSupabaseInstance();
|
|
2085
|
+
const { error } = await supabase.schema("ai").from("chat_threads").delete().eq("id", threadId);
|
|
2086
|
+
if (error) throw error;
|
|
2087
|
+
}
|
|
2088
|
+
var AuthContext = React14.createContext({
|
|
2089
|
+
session: null,
|
|
2090
|
+
user: null,
|
|
2091
|
+
loading: true,
|
|
2092
|
+
error: null,
|
|
2093
|
+
signOut: async () => {
|
|
2094
|
+
}
|
|
2095
|
+
});
|
|
2096
|
+
var useAuth = () => React14.useContext(AuthContext);
|
|
2097
|
+
var AuthProvider = ({ children }) => {
|
|
2098
|
+
const supabase = useSupabase();
|
|
2099
|
+
const { authConfig } = useDashboardConfig();
|
|
2100
|
+
const [session, setSession] = React14.useState(null);
|
|
2101
|
+
const [user, setUser] = React14.useState(null);
|
|
2102
|
+
const [loading, setLoading] = React14.useState(true);
|
|
2103
|
+
const [error, setError] = React14.useState(null);
|
|
2104
|
+
const router$1 = router.useRouter();
|
|
2105
|
+
const userProfileTable = authConfig?.userProfileTable;
|
|
2106
|
+
const roleColumn = authConfig?.roleColumn || "role";
|
|
2107
|
+
const fetchUserDetails = React14.useCallback(async (supabaseUser) => {
|
|
2108
|
+
if (!supabaseUser) return null;
|
|
2109
|
+
const basicUser = {
|
|
2110
|
+
id: supabaseUser.id,
|
|
2111
|
+
email: supabaseUser.email
|
|
2112
|
+
};
|
|
2113
|
+
if (!userProfileTable || !supabase) return basicUser;
|
|
2114
|
+
try {
|
|
2115
|
+
const timeoutPromise = new Promise(
|
|
2116
|
+
(_, reject) => setTimeout(() => reject(new Error("Profile fetch timeout")), 5e3)
|
|
2117
|
+
);
|
|
2118
|
+
const fetchPromise = supabase.from(userProfileTable).select(roleColumn).eq("id", supabaseUser.id).single();
|
|
2119
|
+
const { data: profile, error: profileError } = await Promise.race([
|
|
2120
|
+
fetchPromise,
|
|
2121
|
+
timeoutPromise
|
|
2122
|
+
]);
|
|
2123
|
+
if (profileError) {
|
|
2124
|
+
if (profileError.message.includes("does not exist") || profileError.message.includes("No rows found") || profileError.code === "PGRST116") {
|
|
2125
|
+
console.log("User profile table not found or user not in table, using basic auth info");
|
|
2126
|
+
return basicUser;
|
|
2127
|
+
}
|
|
2128
|
+
console.error("Error fetching user profile:", profileError);
|
|
2129
|
+
return basicUser;
|
|
2130
|
+
}
|
|
2131
|
+
const roleValue = profile ? profile[roleColumn] : void 0;
|
|
2132
|
+
return { ...basicUser, role: roleValue };
|
|
2133
|
+
} catch (err) {
|
|
2134
|
+
console.error("Error fetching user profile:", err);
|
|
2135
|
+
return basicUser;
|
|
2136
|
+
}
|
|
2137
|
+
}, [supabase, userProfileTable, roleColumn]);
|
|
2138
|
+
React14.useEffect(() => {
|
|
2139
|
+
if (!supabase) return;
|
|
2140
|
+
let mounted = true;
|
|
2141
|
+
const safetyTimeout = setTimeout(() => {
|
|
2142
|
+
if (mounted) {
|
|
2143
|
+
console.warn("Auth initialization taking too long, forcing loading to false");
|
|
2144
|
+
setLoading(false);
|
|
2145
|
+
}
|
|
2146
|
+
}, 1e4);
|
|
2147
|
+
const initializeAuth = async () => {
|
|
2148
|
+
try {
|
|
2149
|
+
const { data: { session: initialSession }, error: sessionError } = await supabase.auth.getSession();
|
|
2150
|
+
if (!mounted) return;
|
|
2151
|
+
if (sessionError) {
|
|
2152
|
+
setError(sessionError);
|
|
2153
|
+
setLoading(false);
|
|
2154
|
+
clearTimeout(safetyTimeout);
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
setSession(initialSession);
|
|
2158
|
+
setLoading(false);
|
|
2159
|
+
if (initialSession?.user) {
|
|
2160
|
+
try {
|
|
2161
|
+
const userDetails = await fetchUserDetails(initialSession.user);
|
|
2162
|
+
if (mounted) {
|
|
2163
|
+
setUser(userDetails);
|
|
2164
|
+
if (userDetails) {
|
|
2165
|
+
identifyCoreUser(userDetails.id, {
|
|
2166
|
+
email: userDetails.email,
|
|
2167
|
+
name: userDetails.email,
|
|
2168
|
+
// using email as the display name for now
|
|
2169
|
+
role: userDetails.role
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
} catch (err) {
|
|
2174
|
+
console.error("Error fetching user details during init:", err);
|
|
2175
|
+
if (mounted) {
|
|
2176
|
+
setUser({
|
|
2177
|
+
id: initialSession.user.id,
|
|
2178
|
+
email: initialSession.user.email
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
} catch (err) {
|
|
2184
|
+
if (mounted) setError(err instanceof Error ? err : new Error("Failed to initialize auth"));
|
|
2185
|
+
} finally {
|
|
2186
|
+
if (mounted) {
|
|
2187
|
+
setLoading(false);
|
|
2188
|
+
clearTimeout(safetyTimeout);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
};
|
|
2192
|
+
initializeAuth();
|
|
2193
|
+
const { data: { subscription } } = supabase.auth.onAuthStateChange(async (_event, currentSession) => {
|
|
2194
|
+
if (!mounted) return;
|
|
2195
|
+
setSession(currentSession);
|
|
2196
|
+
setUser(null);
|
|
2197
|
+
setLoading(false);
|
|
2198
|
+
if (currentSession?.user) {
|
|
2199
|
+
try {
|
|
2200
|
+
const userDetails = await fetchUserDetails(currentSession.user);
|
|
2201
|
+
if (mounted) {
|
|
2202
|
+
setUser(userDetails);
|
|
2203
|
+
if (userDetails) {
|
|
2204
|
+
identifyCoreUser(userDetails.id, {
|
|
2205
|
+
email: userDetails.email,
|
|
2206
|
+
name: userDetails.email,
|
|
2207
|
+
role: userDetails.role
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
} catch (err) {
|
|
2212
|
+
console.error("Error fetching user details on auth state change:", err);
|
|
2213
|
+
if (mounted) {
|
|
2214
|
+
setUser({
|
|
2215
|
+
id: currentSession.user.id,
|
|
2216
|
+
email: currentSession.user.email
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
if (mounted) setLoading(false);
|
|
2222
|
+
});
|
|
2223
|
+
return () => {
|
|
2224
|
+
mounted = false;
|
|
2225
|
+
clearTimeout(safetyTimeout);
|
|
2226
|
+
subscription?.unsubscribe();
|
|
2227
|
+
};
|
|
2228
|
+
}, [supabase, fetchUserDetails]);
|
|
2229
|
+
const signOut = async () => {
|
|
2230
|
+
if (!supabase) return;
|
|
2231
|
+
setLoading(true);
|
|
2232
|
+
const { error: signOutError } = await supabase.auth.signOut();
|
|
2233
|
+
if (signOutError) setError(signOutError);
|
|
2234
|
+
const logoutRedirectPath = authConfig?.defaultLogoutRedirect || "/login";
|
|
2235
|
+
if (router$1 && router$1.pathname !== logoutRedirectPath && !router$1.pathname.startsWith(logoutRedirectPath)) {
|
|
2236
|
+
router$1.replace(logoutRedirectPath);
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AuthContext.Provider, { value: { session, user, loading, error, signOut }, children });
|
|
2240
|
+
};
|
|
2241
|
+
var defaultContextValue = {
|
|
2242
|
+
components: {},
|
|
2243
|
+
hooks: {},
|
|
2244
|
+
pages: {}
|
|
2245
|
+
};
|
|
2246
|
+
var DashboardOverridesContext = React14.createContext(defaultContextValue);
|
|
2247
|
+
var DashboardOverridesProvider = ({
|
|
2248
|
+
overrides = defaultContextValue,
|
|
2249
|
+
children
|
|
2250
|
+
}) => {
|
|
2251
|
+
const normalizedOverrides = React14.useMemo(() => {
|
|
2252
|
+
return {
|
|
2253
|
+
components: overrides.components || {},
|
|
2254
|
+
hooks: overrides.hooks || {},
|
|
2255
|
+
pages: overrides.pages || {}
|
|
2256
|
+
};
|
|
2257
|
+
}, [overrides]);
|
|
2258
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DashboardOverridesContext.Provider, { value: normalizedOverrides, children });
|
|
2259
|
+
};
|
|
2260
|
+
function useComponentOverride(key, Default) {
|
|
2261
|
+
const { components = {} } = React14.useContext(DashboardOverridesContext);
|
|
2262
|
+
return components[key] ?? Default;
|
|
2263
|
+
}
|
|
2264
|
+
function useHookOverride(key, Default) {
|
|
2265
|
+
const { hooks = {} } = React14.useContext(DashboardOverridesContext);
|
|
2266
|
+
return hooks[key] ?? Default;
|
|
2267
|
+
}
|
|
2268
|
+
function usePageOverride(key, Default) {
|
|
2269
|
+
const { pages = {} } = React14.useContext(DashboardOverridesContext);
|
|
2270
|
+
return pages[key] ?? Default;
|
|
2271
|
+
}
|
|
2272
|
+
function useOverrides() {
|
|
2273
|
+
return React14.useContext(DashboardOverridesContext);
|
|
2274
|
+
}
|
|
2275
|
+
var SupabaseContext = React14.createContext(void 0);
|
|
2276
|
+
var SupabaseProvider = ({ client, children }) => {
|
|
2277
|
+
_setSupabaseInstance(client);
|
|
2278
|
+
React14.useEffect(() => {
|
|
2279
|
+
_setSupabaseInstance(client);
|
|
2280
|
+
}, [client]);
|
|
2281
|
+
const contextValue = React14.useMemo(() => ({ supabase: client }), [client]);
|
|
2282
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SupabaseContext.Provider, { value: contextValue, children });
|
|
2283
|
+
};
|
|
2284
|
+
var useSupabase = () => {
|
|
2285
|
+
const context = React14.useContext(SupabaseContext);
|
|
2286
|
+
if (context === void 0) {
|
|
2287
|
+
throw new Error("useSupabase must be used within a SupabaseProvider.");
|
|
2288
|
+
}
|
|
2289
|
+
return context.supabase;
|
|
2290
|
+
};
|
|
2291
|
+
var DEFAULT_COMPANY_ID = "default-company-id";
|
|
2292
|
+
var useWorkspaceMetrics = (workspaceId) => {
|
|
2293
|
+
const supabase = useSupabase();
|
|
2294
|
+
const entityConfig = useEntityConfig();
|
|
2295
|
+
useDatabaseConfig();
|
|
2296
|
+
const dateTimeConfig = useDateTimeConfig();
|
|
2297
|
+
const [workspaceMetrics, setWorkspaceMetrics] = React14.useState(null);
|
|
2298
|
+
const [isLoading, setIsLoading] = React14.useState(true);
|
|
2299
|
+
const [error, setError] = React14.useState(null);
|
|
2300
|
+
const fetchWorkspaceMetrics = React14.useCallback(async () => {
|
|
2301
|
+
try {
|
|
2302
|
+
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
2303
|
+
const { data, error: fetchError } = await supabase.from("overview_workspace_metrics").select("*").eq("workspace_id", workspaceId).eq("date", operationalDate).single();
|
|
2304
|
+
if (fetchError) throw fetchError;
|
|
2305
|
+
setWorkspaceMetrics(data);
|
|
2306
|
+
} catch (err) {
|
|
2307
|
+
setError({ message: err.message, code: err.code });
|
|
2308
|
+
console.error("Error fetching workspace metrics:", err);
|
|
2309
|
+
} finally {
|
|
2310
|
+
setIsLoading(false);
|
|
2311
|
+
}
|
|
2312
|
+
}, [supabase, workspaceId, dateTimeConfig.defaultTimezone]);
|
|
2313
|
+
React14.useEffect(() => {
|
|
2314
|
+
let channels = [];
|
|
2315
|
+
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
2316
|
+
const setupSubscriptions = () => {
|
|
2317
|
+
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
2318
|
+
const metricsTablePrefix = getMetricsTablePrefix();
|
|
2319
|
+
const metricsTable = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2320
|
+
const metricsChannel = supabase.channel("workspace-metrics").on(
|
|
2321
|
+
"postgres_changes",
|
|
2322
|
+
{
|
|
2323
|
+
event: "*",
|
|
2324
|
+
schema: "public",
|
|
2325
|
+
table: metricsTable,
|
|
2326
|
+
filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
|
|
2327
|
+
},
|
|
2328
|
+
async (payload) => {
|
|
2329
|
+
console.log(`Received ${metricsTablePrefix} update:`, payload);
|
|
2330
|
+
await fetchWorkspaceMetrics();
|
|
2331
|
+
}
|
|
2332
|
+
).subscribe((status) => {
|
|
2333
|
+
console.log(`${metricsTablePrefix} subscription status:`, status);
|
|
2334
|
+
});
|
|
2335
|
+
const overviewChannel = supabase.channel("workspace-overview-metrics").on(
|
|
2336
|
+
"postgres_changes",
|
|
2337
|
+
{
|
|
2338
|
+
event: "*",
|
|
2339
|
+
schema: "public",
|
|
2340
|
+
table: "overview_workspace_metrics",
|
|
2341
|
+
filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
|
|
2342
|
+
},
|
|
2343
|
+
async (payload) => {
|
|
2344
|
+
console.log("Received overview metrics update:", payload);
|
|
2345
|
+
await fetchWorkspaceMetrics();
|
|
2346
|
+
}
|
|
2347
|
+
).subscribe((status) => {
|
|
2348
|
+
console.log("Overview metrics subscription status:", status);
|
|
2349
|
+
});
|
|
2350
|
+
channels = [metricsChannel, overviewChannel];
|
|
2351
|
+
};
|
|
2352
|
+
fetchWorkspaceMetrics();
|
|
2353
|
+
setupSubscriptions();
|
|
2354
|
+
return () => {
|
|
2355
|
+
channels.forEach((channel) => {
|
|
2356
|
+
console.log("Cleaning up channel subscription");
|
|
2357
|
+
supabase.removeChannel(channel);
|
|
2358
|
+
});
|
|
2359
|
+
};
|
|
2360
|
+
}, [supabase, workspaceId, fetchWorkspaceMetrics, entityConfig.companyId, dateTimeConfig.defaultTimezone]);
|
|
2361
|
+
return { workspaceMetrics, isLoading, error, refetch: fetchWorkspaceMetrics };
|
|
2362
|
+
};
|
|
2363
|
+
var useLineMetrics = (lineId) => {
|
|
2364
|
+
const supabase = useSupabase();
|
|
2365
|
+
const dateTimeConfig = useDateTimeConfig();
|
|
2366
|
+
const [lineMetrics, setLineMetrics] = React14.useState(null);
|
|
2367
|
+
const [isLoading, setIsLoading] = React14.useState(true);
|
|
2368
|
+
const [error, setError] = React14.useState(null);
|
|
2369
|
+
const fetchLineMetrics = React14.useCallback(async () => {
|
|
2370
|
+
try {
|
|
2371
|
+
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
2372
|
+
const { data, error: fetchError } = await supabase.from("overview_line_metrics").select("*").eq("line_id", lineId).eq("date", operationalDate).single();
|
|
2373
|
+
if (fetchError) throw fetchError;
|
|
2374
|
+
setLineMetrics(data);
|
|
2375
|
+
} catch (err) {
|
|
2376
|
+
setError({ message: err.message, code: err.code });
|
|
2377
|
+
console.error("Error fetching line metrics:", err);
|
|
2378
|
+
} finally {
|
|
2379
|
+
setIsLoading(false);
|
|
2380
|
+
}
|
|
2381
|
+
}, [supabase, lineId, dateTimeConfig.defaultTimezone]);
|
|
2382
|
+
React14.useEffect(() => {
|
|
2383
|
+
let channels = [];
|
|
2384
|
+
const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
2385
|
+
const setupSubscriptions = () => {
|
|
2386
|
+
const lineMetricsChannel = supabase.channel("line-base-metrics").on(
|
|
2387
|
+
"postgres_changes",
|
|
2388
|
+
{
|
|
2389
|
+
event: "*",
|
|
2390
|
+
schema: "public",
|
|
2391
|
+
table: "line_metrics",
|
|
2392
|
+
filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
|
|
2393
|
+
},
|
|
2394
|
+
async (payload) => {
|
|
2395
|
+
console.log("Received line metrics update:", payload);
|
|
2396
|
+
await fetchLineMetrics();
|
|
2397
|
+
}
|
|
2398
|
+
).subscribe((status) => {
|
|
2399
|
+
console.log("Line metrics subscription status:", status);
|
|
2400
|
+
});
|
|
2401
|
+
const overviewChannel = supabase.channel("line-overview-metrics").on(
|
|
2402
|
+
"postgres_changes",
|
|
2403
|
+
{
|
|
2404
|
+
event: "*",
|
|
2405
|
+
schema: "public",
|
|
2406
|
+
table: "overview_line_metrics",
|
|
2407
|
+
filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
|
|
2408
|
+
},
|
|
2409
|
+
async (payload) => {
|
|
2410
|
+
console.log("Received line overview update:", payload);
|
|
2411
|
+
await fetchLineMetrics();
|
|
2412
|
+
}
|
|
2413
|
+
).subscribe((status) => {
|
|
2414
|
+
console.log("Line overview subscription status:", status);
|
|
2415
|
+
});
|
|
2416
|
+
channels = [lineMetricsChannel, overviewChannel];
|
|
2417
|
+
};
|
|
2418
|
+
fetchLineMetrics();
|
|
2419
|
+
setupSubscriptions();
|
|
2420
|
+
return () => {
|
|
2421
|
+
channels.forEach((channel) => {
|
|
2422
|
+
console.log("Cleaning up channel subscription");
|
|
2423
|
+
supabase.removeChannel(channel);
|
|
2424
|
+
});
|
|
2425
|
+
};
|
|
2426
|
+
}, [supabase, lineId, fetchLineMetrics, dateTimeConfig.defaultTimezone]);
|
|
2427
|
+
return { lineMetrics, isLoading, error, refetch: fetchLineMetrics };
|
|
2428
|
+
};
|
|
2429
|
+
var useMetrics = (tableName, options) => {
|
|
2430
|
+
const supabase = useSupabase();
|
|
2431
|
+
const entityConfig = useEntityConfig();
|
|
2432
|
+
const [data, setData] = React14.useState([]);
|
|
2433
|
+
const [isLoading, setIsLoading] = React14.useState(true);
|
|
2434
|
+
const [error, setError] = React14.useState(null);
|
|
2435
|
+
const channelRef = React14.useRef(null);
|
|
2436
|
+
React14.useEffect(() => {
|
|
2437
|
+
const fetchData = async () => {
|
|
2438
|
+
try {
|
|
2439
|
+
setIsLoading(true);
|
|
2440
|
+
setError(null);
|
|
2441
|
+
let actualTableName = tableName;
|
|
2442
|
+
if (tableName === "metrics") {
|
|
2443
|
+
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
2444
|
+
const metricsTablePrefix = getMetricsTablePrefix(companyId);
|
|
2445
|
+
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2446
|
+
}
|
|
2447
|
+
let query = supabase.from(actualTableName).select("*");
|
|
2448
|
+
if (options?.filter) {
|
|
2449
|
+
Object.entries(options.filter).forEach(([key, value]) => {
|
|
2450
|
+
query = query.eq(key, value);
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
const { data: result, error: fetchError } = await query;
|
|
2454
|
+
if (fetchError) throw fetchError;
|
|
2455
|
+
setData(result);
|
|
2456
|
+
} catch (err) {
|
|
2457
|
+
console.error(`Error fetching data from ${tableName}:`, err);
|
|
2458
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
2459
|
+
} finally {
|
|
2460
|
+
setIsLoading(false);
|
|
2461
|
+
}
|
|
2462
|
+
};
|
|
2463
|
+
const setupSubscription = () => {
|
|
2464
|
+
if (!options?.realtime) return;
|
|
2465
|
+
let actualTableName = tableName;
|
|
2466
|
+
if (tableName === "metrics") {
|
|
2467
|
+
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
2468
|
+
const metricsTablePrefix = getMetricsTablePrefix();
|
|
2469
|
+
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2470
|
+
}
|
|
2471
|
+
const filter2 = {};
|
|
2472
|
+
if (options?.filter) {
|
|
2473
|
+
Object.entries(options.filter).forEach(([key, value]) => {
|
|
2474
|
+
filter2[`${key}=eq.${value}`] = value;
|
|
2475
|
+
});
|
|
2476
|
+
}
|
|
2477
|
+
channelRef.current = supabase.channel(`${tableName}-changes`).on(
|
|
2478
|
+
"postgres_changes",
|
|
2479
|
+
{
|
|
2480
|
+
event: "*",
|
|
2481
|
+
schema: "public",
|
|
2482
|
+
table: actualTableName,
|
|
2483
|
+
filter: Object.keys(filter2).length > 0 ? Object.keys(filter2).join(",") : void 0
|
|
2484
|
+
},
|
|
2485
|
+
() => {
|
|
2486
|
+
fetchData();
|
|
2487
|
+
}
|
|
2488
|
+
).subscribe();
|
|
2489
|
+
};
|
|
2490
|
+
fetchData();
|
|
2491
|
+
setupSubscription();
|
|
2492
|
+
return () => {
|
|
2493
|
+
if (channelRef.current) {
|
|
2494
|
+
supabase.removeChannel(channelRef.current);
|
|
2495
|
+
}
|
|
2496
|
+
};
|
|
2497
|
+
}, [supabase, tableName, options, entityConfig.companyId]);
|
|
2498
|
+
const refetch = async () => {
|
|
2499
|
+
setIsLoading(true);
|
|
2500
|
+
try {
|
|
2501
|
+
let actualTableName = tableName;
|
|
2502
|
+
if (tableName === "metrics") {
|
|
2503
|
+
const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
|
|
2504
|
+
const metricsTablePrefix = getMetricsTablePrefix(companyId);
|
|
2505
|
+
actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2506
|
+
}
|
|
2507
|
+
let query = supabase.from(actualTableName).select("*");
|
|
2508
|
+
if (options?.filter) {
|
|
2509
|
+
Object.entries(options.filter).forEach(([key, value]) => {
|
|
2510
|
+
query = query.eq(key, value);
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
const { data: result, error: fetchError } = await query;
|
|
2514
|
+
if (fetchError) throw fetchError;
|
|
2515
|
+
setData(result);
|
|
2516
|
+
setError(null);
|
|
2517
|
+
} catch (err) {
|
|
2518
|
+
console.error(`Error refetching data from ${tableName}:`, err);
|
|
2519
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
2520
|
+
} finally {
|
|
2521
|
+
setIsLoading(false);
|
|
2522
|
+
}
|
|
2523
|
+
};
|
|
2524
|
+
return { data, isLoading, error, refetch };
|
|
2525
|
+
};
|
|
2526
|
+
var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId) => {
|
|
2527
|
+
const entityConfig = useEntityConfig();
|
|
2528
|
+
const databaseConfig = useDatabaseConfig();
|
|
2529
|
+
const dateTimeConfig = useDateTimeConfig();
|
|
2530
|
+
const shiftConfig = useShiftConfig();
|
|
2531
|
+
const workspaceConfig = useWorkspaceConfig();
|
|
2532
|
+
const supabase = useSupabase();
|
|
2533
|
+
const [metrics2, setMetrics] = React14.useState(null);
|
|
2534
|
+
const [isLoading, setIsLoading] = React14.useState(true);
|
|
2535
|
+
const [error, setError] = React14.useState(null);
|
|
2536
|
+
const updateQueueRef = React14.useRef(false);
|
|
2537
|
+
const isFetchingRef = React14.useRef(false);
|
|
2538
|
+
const timeoutRef = React14.useRef(null);
|
|
2539
|
+
const channelRef = React14.useRef(null);
|
|
2540
|
+
const schema = databaseConfig.schema ?? "public";
|
|
2541
|
+
const companyId = entityConfig.companyId || "";
|
|
2542
|
+
const metricsTablePrefix = getMetricsTablePrefix();
|
|
2543
|
+
const metricsTable = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2544
|
+
const defaultTimezone = dateTimeConfig.defaultTimezone;
|
|
2545
|
+
const workspaceMetricsBaseTable = databaseConfig.tables?.workspaces ?? "workspace_metrics";
|
|
2546
|
+
const workspaceActionsTable = databaseConfig.tables?.actions ?? "workspace_actions";
|
|
2547
|
+
const fetchMetrics = React14.useCallback(async () => {
|
|
2548
|
+
if (!workspaceId || isFetchingRef.current) return;
|
|
2549
|
+
try {
|
|
2550
|
+
isFetchingRef.current = true;
|
|
2551
|
+
const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
|
|
2552
|
+
const queryDate = date || currentShift.date;
|
|
2553
|
+
const queryShiftId = shiftId !== void 0 ? shiftId : currentShift.shiftId;
|
|
2554
|
+
console.log("[useWorkspaceDetailedMetrics] Using shift ID:", queryShiftId, "from input shift:", shiftId);
|
|
2555
|
+
console.log("[useWorkspaceDetailedMetrics] Using date:", queryDate, "from input date:", date);
|
|
2556
|
+
console.log(`[useWorkspaceDetailedMetrics] Querying ${metricsTable} for workspace: ${workspaceId}, date: ${queryDate}, shift: ${queryShiftId}`);
|
|
2557
|
+
const { data, error: fetchError } = await supabase.from(metricsTable).select("*").eq("workspace_id", workspaceId).eq("date", queryDate).eq("shift_id", queryShiftId).maybeSingle();
|
|
2558
|
+
if (fetchError) throw fetchError;
|
|
2559
|
+
if (!data && !date && shiftId === void 0) {
|
|
2560
|
+
console.log("[useWorkspaceDetailedMetrics] No data found for current date/shift, attempting to find most recent data...");
|
|
2561
|
+
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();
|
|
2562
|
+
if (recentError) throw recentError;
|
|
2563
|
+
if (recentData) {
|
|
2564
|
+
console.log(`[useWorkspaceDetailedMetrics] Found fallback data from date: ${recentData.date}, shift: ${recentData.shift_id}`);
|
|
2565
|
+
const outputDifference2 = (recentData.total_output || 0) - (recentData.ideal_output || 0);
|
|
2566
|
+
const workspaceMatch2 = recentData.workspace_name?.match(/WS(\d+)/);
|
|
2567
|
+
const workspaceNumber2 = workspaceMatch2 ? parseInt(workspaceMatch2[1]) : 0;
|
|
2568
|
+
const specialWsStart2 = workspaceConfig.specialWorkspaces?.startId ?? 19;
|
|
2569
|
+
const specialWsEnd2 = workspaceConfig.specialWorkspaces?.endId ?? 34;
|
|
2570
|
+
const isSpecialWorkspace2 = workspaceNumber2 >= specialWsStart2 && workspaceNumber2 <= specialWsEnd2;
|
|
2571
|
+
const outputHourly2 = recentData.output_hourly || {};
|
|
2572
|
+
const hasOutputHourlyData2 = outputHourly2 && typeof outputHourly2 === "object" && Object.keys(outputHourly2).length > 0;
|
|
2573
|
+
let hourlyActionCounts2 = [];
|
|
2574
|
+
if (hasOutputHourlyData2) {
|
|
2575
|
+
console.log("Using new output_hourly column for workspace (fallback):", recentData.workspace_name);
|
|
2576
|
+
console.log("Raw output_hourly data (fallback):", outputHourly2);
|
|
2577
|
+
const isNightShift = recentData.shift_id === 1;
|
|
2578
|
+
const shiftStart = recentData.shift_start || (isNightShift ? "22:00" : "06:00");
|
|
2579
|
+
const shiftEnd = recentData.shift_end || (isNightShift ? "06:00" : "14:00");
|
|
2580
|
+
const startHour = parseInt(shiftStart.split(":")[0]);
|
|
2581
|
+
let expectedHours = [];
|
|
2582
|
+
if (isNightShift) {
|
|
2583
|
+
for (let i = 0; i < 9; i++) {
|
|
2584
|
+
expectedHours.push((startHour + i) % 24);
|
|
2585
|
+
}
|
|
2586
|
+
} else {
|
|
2587
|
+
for (let i = 0; i < 9; i++) {
|
|
2588
|
+
expectedHours.push((startHour + i) % 24);
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
console.log("Expected shift hours (fallback):", expectedHours);
|
|
2592
|
+
console.log("Available data hours (fallback):", Object.keys(outputHourly2));
|
|
2593
|
+
hourlyActionCounts2 = expectedHours.map((expectedHour) => {
|
|
2594
|
+
let hourData = outputHourly2[expectedHour.toString()];
|
|
2595
|
+
if (!hourData && isNightShift) {
|
|
2596
|
+
for (const [storedHour, data2] of Object.entries(outputHourly2)) {
|
|
2597
|
+
if (Array.isArray(data2) && data2.length > 0 && data2.some((val) => val > 0)) {
|
|
2598
|
+
if (storedHour === "18" && expectedHour === 1) {
|
|
2599
|
+
hourData = data2;
|
|
2600
|
+
console.log(`Mapping stored hour ${storedHour} to expected hour ${expectedHour} (fallback)`);
|
|
2601
|
+
break;
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
return Array.isArray(hourData) ? hourData.reduce((sum, count) => sum + (count || 0), 0) : 0;
|
|
2607
|
+
});
|
|
2608
|
+
console.log("Final hourly action counts (fallback):", hourlyActionCounts2);
|
|
2609
|
+
} else {
|
|
2610
|
+
console.log("Using output_array fallback for workspace (fallback):", recentData.workspace_name);
|
|
2611
|
+
const minuteByMinuteArray = recentData.output_array || [];
|
|
2612
|
+
if (isSpecialWorkspace2) {
|
|
2613
|
+
const last40Readings = minuteByMinuteArray.slice(Math.max(0, minuteByMinuteArray.length - 40));
|
|
2614
|
+
hourlyActionCounts2 = last40Readings;
|
|
2615
|
+
} else {
|
|
2616
|
+
for (let i = 0; i < minuteByMinuteArray.length; i += 60) {
|
|
2617
|
+
const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
|
|
2618
|
+
const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
|
|
2619
|
+
hourlyActionCounts2.push(hourlySum);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
const transformedData2 = {
|
|
2624
|
+
workspace_id: recentData.workspace_id,
|
|
2625
|
+
workspace_name: recentData.workspace_name,
|
|
2626
|
+
line_id: recentData.line_id,
|
|
2627
|
+
line_name: recentData.line_name || "Line 1",
|
|
2628
|
+
company_id: recentData.company_id || companyId,
|
|
2629
|
+
company_name: recentData.company_name || "Nahar Group",
|
|
2630
|
+
date: recentData.date,
|
|
2631
|
+
shift_id: recentData.shift_id,
|
|
2632
|
+
action_name: recentData.action_name || "",
|
|
2633
|
+
shift_start: recentData.shift_start || "06:00",
|
|
2634
|
+
shift_end: recentData.shift_end || "14:00",
|
|
2635
|
+
shift_type: recentData.shift_type || (recentData.shift_id === 0 ? "Day" : "Night"),
|
|
2636
|
+
pph_threshold: recentData.pph_threshold || 0,
|
|
2637
|
+
target_output: recentData.total_day_output || 0,
|
|
2638
|
+
avg_pph: recentData.avg_pph || 0,
|
|
2639
|
+
avg_cycle_time: recentData.avg_cycle_time || 0,
|
|
2640
|
+
ideal_cycle_time: recentData.ideal_cycle_time || 0,
|
|
2641
|
+
avg_efficiency: recentData.efficiency || 0,
|
|
2642
|
+
total_actions: recentData.total_output || 0,
|
|
2643
|
+
hourly_action_counts: hourlyActionCounts2,
|
|
2644
|
+
// Now uses the NEW logic with fallback
|
|
2645
|
+
workspace_rank: recentData.workspace_rank || 0,
|
|
2646
|
+
total_workspaces: recentData.total_workspaces || workspaceConfig.totalWorkspaces || 42,
|
|
2647
|
+
ideal_output_until_now: recentData.ideal_output || 0,
|
|
2648
|
+
output_difference: outputDifference2,
|
|
2649
|
+
idle_time: recentData.idle_time || 0,
|
|
2650
|
+
idle_time_hourly: recentData.idle_time_hourly || void 0,
|
|
2651
|
+
...recentData.compliance_efficiency !== void 0 && { compliance_efficiency: recentData.compliance_efficiency },
|
|
2652
|
+
...recentData.sop_check !== void 0 && { sop_check: recentData.sop_check }
|
|
2653
|
+
};
|
|
2654
|
+
setMetrics(transformedData2);
|
|
2655
|
+
setIsLoading(false);
|
|
2656
|
+
updateQueueRef.current = false;
|
|
2657
|
+
isFetchingRef.current = false;
|
|
2658
|
+
return;
|
|
2659
|
+
} else {
|
|
2660
|
+
console.warn("[useWorkspaceDetailedMetrics] No data found for workspace:", workspaceId, "at all");
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
if (!data) {
|
|
2664
|
+
console.warn("[useWorkspaceDetailedMetrics] No detailed metrics found for workspace:", workspaceId);
|
|
2665
|
+
setMetrics(null);
|
|
2666
|
+
setIsLoading(false);
|
|
2667
|
+
updateQueueRef.current = false;
|
|
2668
|
+
isFetchingRef.current = false;
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
const outputDifference = (data.total_output || 0) - (data.ideal_output || 0);
|
|
2672
|
+
const workspaceMatch = data.workspace_name?.match(/WS(\d+)/);
|
|
2673
|
+
const workspaceNumber = workspaceMatch ? parseInt(workspaceMatch[1]) : 0;
|
|
2674
|
+
const specialWsStart = workspaceConfig.specialWorkspaces?.startId ?? 19;
|
|
2675
|
+
const specialWsEnd = workspaceConfig.specialWorkspaces?.endId ?? 34;
|
|
2676
|
+
const isSpecialWorkspace = workspaceNumber >= specialWsStart && workspaceNumber <= specialWsEnd;
|
|
2677
|
+
const outputHourly = data.output_hourly || {};
|
|
2678
|
+
console.log("[DEBUG] Raw data.output_hourly:", data.output_hourly);
|
|
2679
|
+
console.log("[DEBUG] outputHourly after || {}:", outputHourly);
|
|
2680
|
+
console.log("[DEBUG] typeof outputHourly:", typeof outputHourly);
|
|
2681
|
+
console.log("[DEBUG] Object.keys(outputHourly):", Object.keys(outputHourly));
|
|
2682
|
+
console.log("[DEBUG] Object.keys(outputHourly).length:", Object.keys(outputHourly).length);
|
|
2683
|
+
const hasOutputHourlyData = outputHourly && typeof outputHourly === "object" && Object.keys(outputHourly).length > 0;
|
|
2684
|
+
console.log("[DEBUG] hasOutputHourlyData:", hasOutputHourlyData);
|
|
2685
|
+
let hourlyActionCounts = [];
|
|
2686
|
+
if (hasOutputHourlyData) {
|
|
2687
|
+
console.log("\u2705 Using new output_hourly column for workspace:", data.workspace_name);
|
|
2688
|
+
console.log("Raw output_hourly data:", JSON.stringify(outputHourly));
|
|
2689
|
+
const isNightShift = data.shift_id === 1;
|
|
2690
|
+
const shiftStart = data.shift_start || (isNightShift ? "22:00" : "06:00");
|
|
2691
|
+
const shiftEnd = data.shift_end || (isNightShift ? "06:00" : "14:00");
|
|
2692
|
+
const startHour = parseInt(shiftStart.split(":")[0]);
|
|
2693
|
+
let expectedHours = [];
|
|
2694
|
+
if (isNightShift) {
|
|
2695
|
+
for (let i = 0; i < 9; i++) {
|
|
2696
|
+
expectedHours.push((startHour + i) % 24);
|
|
2697
|
+
}
|
|
2698
|
+
} else {
|
|
2699
|
+
for (let i = 0; i < 9; i++) {
|
|
2700
|
+
expectedHours.push((startHour + i) % 24);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
console.log("Expected shift hours:", expectedHours);
|
|
2704
|
+
console.log("Available data hours:", Object.keys(outputHourly));
|
|
2705
|
+
hourlyActionCounts = expectedHours.map((expectedHour) => {
|
|
2706
|
+
let hourData = outputHourly[expectedHour.toString()];
|
|
2707
|
+
if (!hourData && isNightShift) {
|
|
2708
|
+
for (const [storedHour, data2] of Object.entries(outputHourly)) {
|
|
2709
|
+
if (Array.isArray(data2) && data2.length > 0 && data2.some((val) => val > 0)) {
|
|
2710
|
+
if (storedHour === "18" && expectedHour === 1) {
|
|
2711
|
+
hourData = data2;
|
|
2712
|
+
console.log(`Mapping stored hour ${storedHour} to expected hour ${expectedHour}`);
|
|
2713
|
+
break;
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
return Array.isArray(hourData) ? hourData.reduce((sum, count) => sum + (count || 0), 0) : 0;
|
|
2719
|
+
});
|
|
2720
|
+
console.log("Final hourly action counts:", hourlyActionCounts);
|
|
2721
|
+
} else {
|
|
2722
|
+
console.log("\u274C Using output_array fallback for workspace:", data.workspace_name);
|
|
2723
|
+
console.log("[DEBUG] Fallback reason - hasOutputHourlyData is false");
|
|
2724
|
+
console.log("[DEBUG] data.output_hourly was:", data.output_hourly);
|
|
2725
|
+
const minuteByMinuteArray = data.output_array || [];
|
|
2726
|
+
if (isSpecialWorkspace) {
|
|
2727
|
+
const last40Readings = minuteByMinuteArray.slice(Math.max(0, minuteByMinuteArray.length - 40));
|
|
2728
|
+
hourlyActionCounts = last40Readings;
|
|
2729
|
+
} else {
|
|
2730
|
+
for (let i = 0; i < minuteByMinuteArray.length; i += 60) {
|
|
2731
|
+
const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
|
|
2732
|
+
const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
|
|
2733
|
+
hourlyActionCounts.push(hourlySum);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
console.log("Final hourly action counts:", hourlyActionCounts);
|
|
2737
|
+
}
|
|
2738
|
+
const transformedData = {
|
|
2739
|
+
workspace_id: data.workspace_id,
|
|
2740
|
+
workspace_name: data.workspace_name,
|
|
2741
|
+
line_id: data.line_id,
|
|
2742
|
+
line_name: data.line_name || "Line 1",
|
|
2743
|
+
company_id: data.company_id || companyId,
|
|
2744
|
+
company_name: data.company_name || "Nahar Group",
|
|
2745
|
+
date: data.date,
|
|
2746
|
+
shift_id: data.shift_id,
|
|
2747
|
+
action_name: data.action_name || "",
|
|
2748
|
+
shift_start: data.shift_start || "06:00",
|
|
2749
|
+
shift_end: data.shift_end || "14:00",
|
|
2750
|
+
shift_type: data.shift_type || (data.shift_id === 0 ? "Day" : "Night"),
|
|
2751
|
+
pph_threshold: data.pph_threshold || 0,
|
|
2752
|
+
target_output: data.total_day_output || 0,
|
|
2753
|
+
avg_pph: data.avg_pph || 0,
|
|
2754
|
+
avg_cycle_time: data.avg_cycle_time || 0,
|
|
2755
|
+
ideal_cycle_time: data.ideal_cycle_time || 0,
|
|
2756
|
+
avg_efficiency: data.efficiency || 0,
|
|
2757
|
+
total_actions: data.total_output || 0,
|
|
2758
|
+
hourly_action_counts: hourlyActionCounts,
|
|
2759
|
+
workspace_rank: data.workspace_rank || 0,
|
|
2760
|
+
total_workspaces: data.total_workspaces || workspaceConfig.totalWorkspaces || 42,
|
|
2761
|
+
ideal_output_until_now: data.ideal_output || 0,
|
|
2762
|
+
output_difference: outputDifference,
|
|
2763
|
+
idle_time: data.idle_time || 0,
|
|
2764
|
+
// Add idle_time from performance_metrics table
|
|
2765
|
+
idle_time_hourly: data.idle_time_hourly || void 0,
|
|
2766
|
+
// Add idle_time_hourly from performance_metrics table
|
|
2767
|
+
...data.compliance_efficiency !== void 0 && { compliance_efficiency: data.compliance_efficiency },
|
|
2768
|
+
...data.sop_check !== void 0 && { sop_check: data.sop_check }
|
|
2769
|
+
};
|
|
2770
|
+
setMetrics(transformedData);
|
|
2771
|
+
} catch (err) {
|
|
2772
|
+
console.error("Error fetching workspace metrics:", err);
|
|
2773
|
+
setError({ message: err.message, code: err.code });
|
|
2774
|
+
} finally {
|
|
2775
|
+
isFetchingRef.current = false;
|
|
2776
|
+
updateQueueRef.current = false;
|
|
2777
|
+
setIsLoading(false);
|
|
2778
|
+
}
|
|
2779
|
+
}, [supabase, workspaceId, date, shiftId, metricsTable, defaultTimezone, shiftConfig, workspaceConfig, companyId]);
|
|
2780
|
+
const queueUpdate = React14.useCallback(() => {
|
|
2781
|
+
if (!workspaceId || updateQueueRef.current) return;
|
|
2782
|
+
updateQueueRef.current = true;
|
|
2783
|
+
if (timeoutRef.current) {
|
|
2784
|
+
clearTimeout(timeoutRef.current);
|
|
2785
|
+
}
|
|
2786
|
+
timeoutRef.current = setTimeout(() => {
|
|
2787
|
+
fetchMetrics();
|
|
2788
|
+
}, 500);
|
|
2789
|
+
}, [fetchMetrics, workspaceId]);
|
|
2790
|
+
const setupSubscription = React14.useCallback(() => {
|
|
2791
|
+
if (!workspaceId) return;
|
|
2792
|
+
if (channelRef.current) {
|
|
2793
|
+
supabase.removeChannel(channelRef.current);
|
|
2794
|
+
}
|
|
2795
|
+
channelRef.current = supabase.channel("workspace-detailed-metrics").on(
|
|
2796
|
+
"postgres_changes",
|
|
2797
|
+
{
|
|
2798
|
+
event: "*",
|
|
2799
|
+
schema,
|
|
2800
|
+
table: metricsTable,
|
|
2801
|
+
filter: `workspace_id=eq.${workspaceId}`
|
|
2802
|
+
},
|
|
2803
|
+
async (payload) => {
|
|
2804
|
+
console.log(`Received ${metricsTablePrefix} update:`, payload);
|
|
2805
|
+
await fetchMetrics();
|
|
2806
|
+
}
|
|
2807
|
+
).subscribe((status) => {
|
|
2808
|
+
console.log(`Workspace detailed metrics subscription status:`, status);
|
|
2809
|
+
});
|
|
2810
|
+
}, [supabase, workspaceId, fetchMetrics, schema, metricsTable, metricsTablePrefix]);
|
|
2811
|
+
React14.useEffect(() => {
|
|
2812
|
+
if (!workspaceId) {
|
|
2813
|
+
setIsLoading(false);
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
const channels = [];
|
|
2817
|
+
const operationalDate = date || getOperationalDate();
|
|
2818
|
+
const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
|
|
2819
|
+
const queryShiftId = shiftId ?? currentShift.shiftId;
|
|
2820
|
+
const metricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
|
|
2821
|
+
"postgres_changes",
|
|
2822
|
+
{
|
|
2823
|
+
event: "*",
|
|
2824
|
+
schema,
|
|
2825
|
+
table: metricsTable,
|
|
2826
|
+
filter: `workspace_id=eq.${workspaceId}`
|
|
2827
|
+
},
|
|
2828
|
+
async (payload) => {
|
|
2829
|
+
const payloadData = payload.new;
|
|
2830
|
+
console.log(`Received ${metricsTablePrefix} update:`, {
|
|
2831
|
+
payload,
|
|
2832
|
+
payloadDate: payloadData?.date,
|
|
2833
|
+
payloadShift: payloadData?.shift_id,
|
|
2834
|
+
currentDate: operationalDate,
|
|
2835
|
+
currentShift: queryShiftId,
|
|
2836
|
+
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
2837
|
+
});
|
|
2838
|
+
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
2839
|
+
queueUpdate();
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
).subscribe((status) => {
|
|
2843
|
+
console.log(`${metricsTablePrefix} subscription status:`, status);
|
|
2918
2844
|
});
|
|
2919
|
-
const
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
"Accept": "text/event-stream"
|
|
2845
|
+
const workspaceMetricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
|
|
2846
|
+
"postgres_changes",
|
|
2847
|
+
{
|
|
2848
|
+
event: "*",
|
|
2849
|
+
schema,
|
|
2850
|
+
table: workspaceMetricsBaseTable,
|
|
2851
|
+
filter: `workspace_id=eq.${workspaceId}`
|
|
2927
2852
|
},
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2853
|
+
async (payload) => {
|
|
2854
|
+
const payloadData = payload.new;
|
|
2855
|
+
console.log("Received workspace_metrics update:", {
|
|
2856
|
+
payload,
|
|
2857
|
+
payloadDate: payloadData?.date,
|
|
2858
|
+
payloadShift: payloadData?.shift_id,
|
|
2859
|
+
currentDate: operationalDate,
|
|
2860
|
+
currentShift: queryShiftId,
|
|
2861
|
+
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
2862
|
+
});
|
|
2863
|
+
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
2864
|
+
queueUpdate();
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
).subscribe((status) => {
|
|
2868
|
+
console.log(`Workspace metrics subscription status:`, status);
|
|
2941
2869
|
});
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
}
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2870
|
+
const workspaceActionsChannel = supabase.channel(`workspace-actions-${workspaceId}`).on(
|
|
2871
|
+
"postgres_changes",
|
|
2872
|
+
{
|
|
2873
|
+
event: "*",
|
|
2874
|
+
schema,
|
|
2875
|
+
table: workspaceActionsTable,
|
|
2876
|
+
filter: `workspace_id=eq.${workspaceId}`
|
|
2877
|
+
},
|
|
2878
|
+
async (payload) => {
|
|
2879
|
+
const payloadData = payload.new;
|
|
2880
|
+
console.log("Received workspace_actions update:", {
|
|
2881
|
+
payload,
|
|
2882
|
+
payloadDate: payloadData?.date,
|
|
2883
|
+
payloadShift: payloadData?.shift_id,
|
|
2884
|
+
currentDate: operationalDate,
|
|
2885
|
+
currentShift: queryShiftId,
|
|
2886
|
+
matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
|
|
2887
|
+
});
|
|
2888
|
+
if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
|
|
2889
|
+
queueUpdate();
|
|
2954
2890
|
}
|
|
2955
2891
|
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
}
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2892
|
+
).subscribe((status) => {
|
|
2893
|
+
console.log(`Workspace actions subscription status:`, status);
|
|
2894
|
+
});
|
|
2895
|
+
channels.push(metricsChannel, workspaceMetricsChannel, workspaceActionsChannel);
|
|
2896
|
+
fetchMetrics();
|
|
2897
|
+
setupSubscription();
|
|
2898
|
+
return () => {
|
|
2899
|
+
if (timeoutRef.current) {
|
|
2900
|
+
clearTimeout(timeoutRef.current);
|
|
2901
|
+
}
|
|
2902
|
+
channels.forEach((channel) => {
|
|
2903
|
+
console.log("Cleaning up channel subscription");
|
|
2904
|
+
supabase.removeChannel(channel);
|
|
2905
|
+
});
|
|
2906
|
+
if (channelRef.current) {
|
|
2907
|
+
supabase.removeChannel(channelRef.current);
|
|
2908
|
+
}
|
|
2909
|
+
};
|
|
2910
|
+
}, [supabase, workspaceId, date, shiftId, fetchMetrics, queueUpdate, setupSubscription, metricsTable, workspaceMetricsBaseTable, workspaceActionsTable, defaultTimezone, shiftConfig, schema, metricsTablePrefix]);
|
|
2911
|
+
return {
|
|
2912
|
+
metrics: metrics2,
|
|
2913
|
+
isLoading,
|
|
2914
|
+
error,
|
|
2915
|
+
refetch: fetchMetrics
|
|
2916
|
+
};
|
|
2917
|
+
};
|
|
2918
|
+
var useLineWorkspaceMetrics = (lineId, options) => {
|
|
2919
|
+
const entityConfig = useEntityConfig();
|
|
2920
|
+
const databaseConfig = useDatabaseConfig();
|
|
2921
|
+
const dateTimeConfig = useDateTimeConfig();
|
|
2922
|
+
const shiftConfig = useShiftConfig();
|
|
2923
|
+
const supabase = useSupabase();
|
|
2924
|
+
const [workspaces, setWorkspaces] = React14.useState([]);
|
|
2925
|
+
const [loading, setLoading] = React14.useState(true);
|
|
2926
|
+
const [error, setError] = React14.useState(null);
|
|
2927
|
+
const [initialized, setInitialized] = React14.useState(false);
|
|
2928
|
+
const queryShiftId = React14.useMemo(() => {
|
|
2929
|
+
const currentShift = getCurrentShift(
|
|
2930
|
+
dateTimeConfig.defaultTimezone || "Asia/Kolkata",
|
|
2931
|
+
shiftConfig
|
|
2932
|
+
);
|
|
2933
|
+
return options?.initialShiftId !== void 0 ? options.initialShiftId : currentShift.shiftId;
|
|
2934
|
+
}, [options?.initialShiftId, dateTimeConfig.defaultTimezone, shiftConfig]);
|
|
2935
|
+
const queryDate = React14.useMemo(() => {
|
|
2936
|
+
return options?.initialDate || getOperationalDate(dateTimeConfig.defaultTimezone);
|
|
2937
|
+
}, [options?.initialDate, dateTimeConfig.defaultTimezone]);
|
|
2938
|
+
const metricsTable = React14.useMemo(() => {
|
|
2939
|
+
const companyId = entityConfig.companyId;
|
|
2940
|
+
if (!companyId) return "";
|
|
2941
|
+
const metricsTablePrefix = getMetricsTablePrefix();
|
|
2942
|
+
return `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
|
|
2943
|
+
}, [entityConfig.companyId]);
|
|
2944
|
+
const schema = databaseConfig.schema ?? "public";
|
|
2945
|
+
const fetchWorkspaceMetrics = React14.useCallback(async () => {
|
|
2946
|
+
if (!lineId) return;
|
|
2947
|
+
if (!initialized) {
|
|
2948
|
+
setLoading(true);
|
|
2962
2949
|
}
|
|
2950
|
+
setError(null);
|
|
2963
2951
|
try {
|
|
2964
|
-
|
|
2952
|
+
console.log("Fetching workspace metrics with params:", {
|
|
2953
|
+
lineId,
|
|
2954
|
+
queryDate,
|
|
2955
|
+
queryShiftId,
|
|
2956
|
+
metricsTable
|
|
2957
|
+
});
|
|
2958
|
+
const { data, error: fetchError } = await supabase.from(metricsTable).select(`
|
|
2959
|
+
workspace_name,
|
|
2960
|
+
total_output,
|
|
2961
|
+
avg_pph,
|
|
2962
|
+
efficiency,
|
|
2963
|
+
workspace_id,
|
|
2964
|
+
avg_cycle_time,
|
|
2965
|
+
performance_score,
|
|
2966
|
+
trend_score,
|
|
2967
|
+
line_id,
|
|
2968
|
+
total_day_output
|
|
2969
|
+
`).eq("date", queryDate).eq("shift_id", queryShiftId).eq("line_id", lineId).order("workspace_name", { ascending: true });
|
|
2970
|
+
if (fetchError) throw fetchError;
|
|
2971
|
+
const transformedData = (data || []).map((item) => ({
|
|
2972
|
+
company_id: entityConfig.companyId || "unknown",
|
|
2973
|
+
line_id: item.line_id,
|
|
2974
|
+
shift_id: queryShiftId,
|
|
2975
|
+
date: queryDate,
|
|
2976
|
+
workspace_uuid: item.workspace_id,
|
|
2977
|
+
workspace_name: item.workspace_name,
|
|
2978
|
+
action_count: item.total_output || 0,
|
|
2979
|
+
pph: item.avg_pph || 0,
|
|
2980
|
+
performance_score: item.performance_score || 0,
|
|
2981
|
+
avg_cycle_time: item.avg_cycle_time || 0,
|
|
2982
|
+
trend: item.trend_score === 1 ? 2 : 0,
|
|
2983
|
+
predicted_output: 0,
|
|
2984
|
+
efficiency: item.efficiency || 0,
|
|
2985
|
+
action_threshold: item.total_day_output || 0
|
|
2986
|
+
}));
|
|
2987
|
+
setWorkspaces(transformedData);
|
|
2988
|
+
setInitialized(true);
|
|
2989
|
+
} catch (err) {
|
|
2990
|
+
console.error("Error fetching workspace metrics:", err);
|
|
2991
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
2965
2992
|
} finally {
|
|
2966
|
-
|
|
2993
|
+
setLoading(false);
|
|
2967
2994
|
}
|
|
2968
|
-
}
|
|
2969
|
-
|
|
2970
|
-
if (!
|
|
2971
|
-
|
|
2972
|
-
throw new Error("No response body available for streaming");
|
|
2995
|
+
}, [lineId, queryDate, queryShiftId, metricsTable, supabase, entityConfig.companyId]);
|
|
2996
|
+
React14.useEffect(() => {
|
|
2997
|
+
if (!initialized) {
|
|
2998
|
+
fetchWorkspaceMetrics();
|
|
2973
2999
|
}
|
|
2974
|
-
const
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
const line = lines[i].trim();
|
|
2990
|
-
if (!line) continue;
|
|
2991
|
-
console.log("[SSEClient] Processing line:", line);
|
|
2992
|
-
if (line.startsWith("event:")) {
|
|
2993
|
-
const event = line.slice(6).trim();
|
|
2994
|
-
console.log("[SSEClient] Event type:", event);
|
|
2995
|
-
const nextLine = lines[i + 1];
|
|
2996
|
-
if (nextLine?.startsWith("data:")) {
|
|
2997
|
-
const dataStr = nextLine.slice(5).trim();
|
|
2998
|
-
console.log("[SSEClient] Event data:", dataStr);
|
|
2999
|
-
try {
|
|
3000
|
-
const data = JSON.parse(dataStr);
|
|
3001
|
-
switch (event) {
|
|
3002
|
-
case "thread":
|
|
3003
|
-
callbacks.onThreadCreated?.(data.thread_id);
|
|
3004
|
-
break;
|
|
3005
|
-
case "message":
|
|
3006
|
-
callbacks.onMessage?.(data.text);
|
|
3007
|
-
break;
|
|
3008
|
-
case "reasoning":
|
|
3009
|
-
callbacks.onReasoning?.(data.text);
|
|
3010
|
-
break;
|
|
3011
|
-
case "complete":
|
|
3012
|
-
callbacks.onComplete?.(data.message_id);
|
|
3013
|
-
break;
|
|
3014
|
-
case "error":
|
|
3015
|
-
callbacks.onError?.(data.error);
|
|
3016
|
-
break;
|
|
3017
|
-
}
|
|
3018
|
-
} catch (e) {
|
|
3019
|
-
console.error("[SSEClient] Failed to parse data:", dataStr, e);
|
|
3020
|
-
}
|
|
3021
|
-
i++;
|
|
3022
|
-
}
|
|
3023
|
-
}
|
|
3000
|
+
const setupSubscription = () => {
|
|
3001
|
+
if (!lineId) return null;
|
|
3002
|
+
const filter2 = `line_id=eq.${lineId} AND date=eq.${queryDate} AND shift_id=eq.${queryShiftId}`;
|
|
3003
|
+
console.log("Setting up subscription with filter:", filter2);
|
|
3004
|
+
const channel2 = supabase.channel(`line-workspace-metrics-${Date.now()}`).on(
|
|
3005
|
+
"postgres_changes",
|
|
3006
|
+
{
|
|
3007
|
+
event: "*",
|
|
3008
|
+
schema,
|
|
3009
|
+
table: metricsTable,
|
|
3010
|
+
filter: filter2
|
|
3011
|
+
},
|
|
3012
|
+
async (payload) => {
|
|
3013
|
+
console.log("Workspace metrics update received:", payload);
|
|
3014
|
+
await fetchWorkspaceMetrics();
|
|
3024
3015
|
}
|
|
3016
|
+
).subscribe();
|
|
3017
|
+
return channel2;
|
|
3018
|
+
};
|
|
3019
|
+
const channel = setupSubscription();
|
|
3020
|
+
return () => {
|
|
3021
|
+
if (channel) {
|
|
3022
|
+
supabase.removeChannel(channel);
|
|
3025
3023
|
}
|
|
3026
|
-
}
|
|
3027
|
-
|
|
3024
|
+
};
|
|
3025
|
+
}, [lineId, queryDate, queryShiftId, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema]);
|
|
3026
|
+
React14.useEffect(() => {
|
|
3027
|
+
setInitialized(false);
|
|
3028
|
+
}, [lineId, queryDate, queryShiftId]);
|
|
3029
|
+
const refreshWorkspaces = fetchWorkspaceMetrics;
|
|
3030
|
+
return React14.useMemo(
|
|
3031
|
+
() => ({ workspaces, loading, error, refreshWorkspaces }),
|
|
3032
|
+
[workspaces, loading, error, refreshWorkspaces]
|
|
3033
|
+
);
|
|
3034
|
+
};
|
|
3035
|
+
var useHistoricWorkspaceMetrics = (workspaceId, date, inputShiftId) => {
|
|
3036
|
+
useSupabase();
|
|
3037
|
+
const [metrics2, setMetrics] = React14.useState(null);
|
|
3038
|
+
const [isLoading, setIsLoading] = React14.useState(true);
|
|
3039
|
+
const [error, setError] = React14.useState(null);
|
|
3040
|
+
const fetchAndAnimateMetrics = React14.useCallback(async () => {
|
|
3041
|
+
if (!workspaceId || !date || inputShiftId === void 0) {
|
|
3042
|
+
setMetrics(null);
|
|
3043
|
+
setIsLoading(false);
|
|
3044
|
+
setError(null);
|
|
3045
|
+
return;
|
|
3028
3046
|
}
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3047
|
+
setIsLoading(true);
|
|
3048
|
+
setError(null);
|
|
3049
|
+
try {
|
|
3050
|
+
const fetchedData = await dashboardService.getWorkspaceDetailedMetrics(
|
|
3051
|
+
workspaceId,
|
|
3052
|
+
date,
|
|
3053
|
+
inputShiftId
|
|
3054
|
+
);
|
|
3055
|
+
if (!fetchedData) {
|
|
3056
|
+
console.warn("No historic data found for workspace:", workspaceId, date, inputShiftId);
|
|
3057
|
+
setMetrics(null);
|
|
3058
|
+
setIsLoading(false);
|
|
3059
|
+
return;
|
|
3037
3060
|
}
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3061
|
+
const initialHourlyCounts = fetchedData.hourly_action_counts && Array.isArray(fetchedData.hourly_action_counts) ? new Array(fetchedData.hourly_action_counts.length).fill(0) : [];
|
|
3062
|
+
setMetrics({
|
|
3063
|
+
...fetchedData,
|
|
3064
|
+
hourly_action_counts: initialHourlyCounts
|
|
3065
|
+
});
|
|
3066
|
+
setIsLoading(false);
|
|
3067
|
+
if (fetchedData.hourly_action_counts && fetchedData.hourly_action_counts.length > 0) {
|
|
3068
|
+
const totalSteps = 60;
|
|
3069
|
+
let currentStep = 0;
|
|
3070
|
+
let animationIntervalId = setInterval(() => {
|
|
3071
|
+
currentStep++;
|
|
3072
|
+
const progress6 = currentStep / totalSteps;
|
|
3073
|
+
setMetrics((prevMetrics) => {
|
|
3074
|
+
const currentHourlyData = (fetchedData.hourly_action_counts || []).map(
|
|
3075
|
+
(target) => Math.round(target * progress6)
|
|
3076
|
+
);
|
|
3077
|
+
return {
|
|
3078
|
+
...fetchedData,
|
|
3079
|
+
// Base with all other correct data from the latest fetch
|
|
3080
|
+
hourly_action_counts: currentHourlyData
|
|
3081
|
+
};
|
|
3082
|
+
});
|
|
3083
|
+
if (currentStep >= totalSteps) {
|
|
3084
|
+
if (animationIntervalId) clearInterval(animationIntervalId);
|
|
3085
|
+
setMetrics(fetchedData);
|
|
3086
|
+
}
|
|
3087
|
+
}, 1e3 / 60);
|
|
3088
|
+
} else {
|
|
3089
|
+
setMetrics(fetchedData);
|
|
3041
3090
|
}
|
|
3042
|
-
|
|
3091
|
+
} catch (err) {
|
|
3092
|
+
console.error("Error fetching historic workspace metrics:", err);
|
|
3093
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
3094
|
+
setIsLoading(false);
|
|
3095
|
+
setMetrics(null);
|
|
3043
3096
|
}
|
|
3044
|
-
}
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
}
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
return data;
|
|
3082
|
-
}
|
|
3083
|
-
async function updateThreadTitle(threadId, newTitle) {
|
|
3084
|
-
const supabase = _getSupabaseInstance();
|
|
3085
|
-
const { data, error } = await supabase.schema("ai").from("chat_threads").update({
|
|
3086
|
-
title: newTitle,
|
|
3087
|
-
auto_title: false,
|
|
3088
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3089
|
-
}).eq("id", threadId).select().single();
|
|
3090
|
-
if (error) throw error;
|
|
3091
|
-
return data;
|
|
3092
|
-
}
|
|
3093
|
-
async function deleteThread(threadId) {
|
|
3094
|
-
const supabase = _getSupabaseInstance();
|
|
3095
|
-
const { error } = await supabase.schema("ai").from("chat_threads").delete().eq("id", threadId);
|
|
3096
|
-
if (error) throw error;
|
|
3097
|
-
}
|
|
3098
|
-
|
|
3099
|
-
// src/lib/hooks/useLineDetailedMetrics.ts
|
|
3097
|
+
}, [workspaceId, date, inputShiftId]);
|
|
3098
|
+
React14.useEffect(() => {
|
|
3099
|
+
fetchAndAnimateMetrics();
|
|
3100
|
+
}, [fetchAndAnimateMetrics]);
|
|
3101
|
+
const refetch = React14.useCallback(async () => {
|
|
3102
|
+
if (!workspaceId || !date || inputShiftId === void 0) {
|
|
3103
|
+
setError(null);
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
setIsLoading(true);
|
|
3107
|
+
setError(null);
|
|
3108
|
+
try {
|
|
3109
|
+
const fetchedData = await dashboardService.getWorkspaceDetailedMetrics(
|
|
3110
|
+
workspaceId,
|
|
3111
|
+
date,
|
|
3112
|
+
inputShiftId
|
|
3113
|
+
);
|
|
3114
|
+
if (!fetchedData) {
|
|
3115
|
+
setMetrics(null);
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
setMetrics(fetchedData);
|
|
3119
|
+
} catch (err) {
|
|
3120
|
+
console.error("Error re-fetching historic workspace metrics:", err);
|
|
3121
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
3122
|
+
setMetrics(null);
|
|
3123
|
+
} finally {
|
|
3124
|
+
setIsLoading(false);
|
|
3125
|
+
}
|
|
3126
|
+
}, [workspaceId, date, inputShiftId]);
|
|
3127
|
+
return {
|
|
3128
|
+
metrics: metrics2,
|
|
3129
|
+
isLoading,
|
|
3130
|
+
error,
|
|
3131
|
+
refetch
|
|
3132
|
+
};
|
|
3133
|
+
};
|
|
3100
3134
|
var useLineDetailedMetrics = (lineIdFromProp) => {
|
|
3101
3135
|
const entityConfig = useEntityConfig();
|
|
3102
3136
|
const databaseConfig = useDatabaseConfig();
|
|
@@ -4064,166 +4098,511 @@ var useTargets = (options) => {
|
|
|
4064
4098
|
setIsLoading(false);
|
|
4065
4099
|
return;
|
|
4066
4100
|
}
|
|
4067
|
-
if (!companyId) {
|
|
4068
|
-
setError({ message: "Company ID not configured.", code: "CONFIG_ERROR" });
|
|
4069
|
-
setIsLoading(false);
|
|
4070
|
-
setTargets([]);
|
|
4071
|
-
return;
|
|
4101
|
+
if (!companyId) {
|
|
4102
|
+
setError({ message: "Company ID not configured.", code: "CONFIG_ERROR" });
|
|
4103
|
+
setIsLoading(false);
|
|
4104
|
+
setTargets([]);
|
|
4105
|
+
return;
|
|
4106
|
+
}
|
|
4107
|
+
setIsLoading(true);
|
|
4108
|
+
setError(null);
|
|
4109
|
+
try {
|
|
4110
|
+
let query = supabase.from(targetsTable).select("*");
|
|
4111
|
+
query = query.eq("company_id", companyId);
|
|
4112
|
+
if (options?.entityId) {
|
|
4113
|
+
query = query.eq("entity_id", options.entityId);
|
|
4114
|
+
}
|
|
4115
|
+
if (options?.entityType) {
|
|
4116
|
+
query = query.eq("entity_type", options.entityType);
|
|
4117
|
+
}
|
|
4118
|
+
if (options?.date) {
|
|
4119
|
+
query = query.eq("date", options.date);
|
|
4120
|
+
}
|
|
4121
|
+
if (options?.shiftId !== void 0) {
|
|
4122
|
+
query = query.eq("shift_id", options.shiftId);
|
|
4123
|
+
}
|
|
4124
|
+
if (options?.period) {
|
|
4125
|
+
query = query.eq("period", options.period);
|
|
4126
|
+
}
|
|
4127
|
+
query = query.order("createdAt", { ascending: false });
|
|
4128
|
+
const { data, error: fetchError } = await query;
|
|
4129
|
+
if (fetchError) throw fetchError;
|
|
4130
|
+
setTargets(data || []);
|
|
4131
|
+
} catch (err) {
|
|
4132
|
+
console.error("[useTargets] Error fetching targets:", err);
|
|
4133
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
4134
|
+
setTargets([]);
|
|
4135
|
+
} finally {
|
|
4136
|
+
setIsLoading(false);
|
|
4137
|
+
}
|
|
4138
|
+
}, [supabase, companyId, targetsTable, options]);
|
|
4139
|
+
React14.useEffect(() => {
|
|
4140
|
+
fetchData();
|
|
4141
|
+
}, [fetchData]);
|
|
4142
|
+
return {
|
|
4143
|
+
targets,
|
|
4144
|
+
isLoading,
|
|
4145
|
+
error,
|
|
4146
|
+
refetch: fetchData
|
|
4147
|
+
};
|
|
4148
|
+
};
|
|
4149
|
+
var DEFAULT_SHIFTS_TABLE_NAME = "shift_configurations";
|
|
4150
|
+
var useShifts = () => {
|
|
4151
|
+
const { supabaseUrl, supabaseKey } = useDashboardConfig();
|
|
4152
|
+
const { companyId } = useEntityConfig();
|
|
4153
|
+
const { tables } = useDatabaseConfig();
|
|
4154
|
+
const [shifts, setShifts] = React14.useState([]);
|
|
4155
|
+
const [isLoading, setIsLoading] = React14.useState(true);
|
|
4156
|
+
const [error, setError] = React14.useState(null);
|
|
4157
|
+
const supabase = React14.useMemo(() => {
|
|
4158
|
+
if (!supabaseUrl || !supabaseKey) return null;
|
|
4159
|
+
return supabaseJs.createClient(supabaseUrl, supabaseKey);
|
|
4160
|
+
}, [supabaseUrl, supabaseKey]);
|
|
4161
|
+
const shiftsTable = tables?.shiftConfigurations || DEFAULT_SHIFTS_TABLE_NAME;
|
|
4162
|
+
const fetchData = React14.useCallback(async () => {
|
|
4163
|
+
if (!supabase) {
|
|
4164
|
+
setError({ message: "Supabase client not initialized.", code: "CLIENT_INIT_ERROR" });
|
|
4165
|
+
setIsLoading(false);
|
|
4166
|
+
return;
|
|
4167
|
+
}
|
|
4168
|
+
if (!companyId) {
|
|
4169
|
+
setError({ message: "Company ID not configured.", code: "CONFIG_ERROR" });
|
|
4170
|
+
setIsLoading(false);
|
|
4171
|
+
setShifts([]);
|
|
4172
|
+
return;
|
|
4173
|
+
}
|
|
4174
|
+
setIsLoading(true);
|
|
4175
|
+
setError(null);
|
|
4176
|
+
try {
|
|
4177
|
+
let query = supabase.from(shiftsTable).select("*");
|
|
4178
|
+
query = query.eq("company_id", companyId);
|
|
4179
|
+
query = query.order("startTime", { ascending: true });
|
|
4180
|
+
const { data, error: fetchError } = await query;
|
|
4181
|
+
if (fetchError) throw fetchError;
|
|
4182
|
+
setShifts(data || []);
|
|
4183
|
+
} catch (err) {
|
|
4184
|
+
console.error("[useShifts] Error fetching shift configurations:", err);
|
|
4185
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
4186
|
+
setShifts([]);
|
|
4187
|
+
} finally {
|
|
4188
|
+
setIsLoading(false);
|
|
4189
|
+
}
|
|
4190
|
+
}, [supabase, companyId, shiftsTable]);
|
|
4191
|
+
React14.useEffect(() => {
|
|
4192
|
+
fetchData();
|
|
4193
|
+
}, [fetchData]);
|
|
4194
|
+
return {
|
|
4195
|
+
shifts,
|
|
4196
|
+
isLoading,
|
|
4197
|
+
error,
|
|
4198
|
+
refetch: fetchData
|
|
4199
|
+
};
|
|
4200
|
+
};
|
|
4201
|
+
var DEFAULT_OPERATORS_TABLE_NAME = "workspace_operator_assignments";
|
|
4202
|
+
var useWorkspaceOperators = (workspaceId, options) => {
|
|
4203
|
+
const { companyId } = useEntityConfig();
|
|
4204
|
+
const supabase = useSupabase();
|
|
4205
|
+
const [operators, setOperators] = React14.useState([]);
|
|
4206
|
+
const [isLoading, setIsLoading] = React14.useState(true);
|
|
4207
|
+
const [error, setError] = React14.useState(null);
|
|
4208
|
+
const operatorsTable = DEFAULT_OPERATORS_TABLE_NAME;
|
|
4209
|
+
const fetchData = React14.useCallback(async () => {
|
|
4210
|
+
if (!workspaceId) {
|
|
4211
|
+
setError({ message: "Workspace ID is required.", code: "MISSING_PARAM" });
|
|
4212
|
+
setIsLoading(false);
|
|
4213
|
+
setOperators([]);
|
|
4214
|
+
return;
|
|
4215
|
+
}
|
|
4216
|
+
if (!supabase) {
|
|
4217
|
+
setError({ message: "Supabase client not initialized.", code: "CLIENT_INIT_ERROR" });
|
|
4218
|
+
setIsLoading(false);
|
|
4219
|
+
return;
|
|
4220
|
+
}
|
|
4221
|
+
if (!companyId) {
|
|
4222
|
+
setError({ message: "Company ID not configured.", code: "CONFIG_ERROR" });
|
|
4223
|
+
setIsLoading(false);
|
|
4224
|
+
setOperators([]);
|
|
4225
|
+
return;
|
|
4226
|
+
}
|
|
4227
|
+
setIsLoading(true);
|
|
4228
|
+
setError(null);
|
|
4229
|
+
try {
|
|
4230
|
+
let query = supabase.from(operatorsTable).select("*");
|
|
4231
|
+
query = query.eq("company_id", companyId);
|
|
4232
|
+
query = query.eq("workspace_id", workspaceId);
|
|
4233
|
+
if (options?.date) {
|
|
4234
|
+
query = query.eq("date", options.date);
|
|
4235
|
+
}
|
|
4236
|
+
if (options?.shiftId !== void 0) {
|
|
4237
|
+
query = query.eq("shift_id", options.shiftId);
|
|
4238
|
+
}
|
|
4239
|
+
query = query.order("createdAt", { ascending: false });
|
|
4240
|
+
const { data, error: fetchError } = await query;
|
|
4241
|
+
if (fetchError) throw fetchError;
|
|
4242
|
+
setOperators(data || []);
|
|
4243
|
+
} catch (err) {
|
|
4244
|
+
console.error("[useWorkspaceOperators] Error fetching operators:", err);
|
|
4245
|
+
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
4246
|
+
setOperators([]);
|
|
4247
|
+
} finally {
|
|
4248
|
+
setIsLoading(false);
|
|
4249
|
+
}
|
|
4250
|
+
}, [supabase, companyId, workspaceId, operatorsTable, options]);
|
|
4251
|
+
React14.useEffect(() => {
|
|
4252
|
+
fetchData();
|
|
4253
|
+
}, [fetchData]);
|
|
4254
|
+
return {
|
|
4255
|
+
operators,
|
|
4256
|
+
isLoading,
|
|
4257
|
+
error,
|
|
4258
|
+
refetch: fetchData
|
|
4259
|
+
};
|
|
4260
|
+
};
|
|
4261
|
+
|
|
4262
|
+
// src/lib/utils/dashboardReload.ts
|
|
4263
|
+
var createThrottledReload = (interval = 5e3) => {
|
|
4264
|
+
let last = 0;
|
|
4265
|
+
let queued = false;
|
|
4266
|
+
const doReload = () => {
|
|
4267
|
+
if (typeof window !== "undefined") {
|
|
4268
|
+
window.location.reload();
|
|
4269
|
+
}
|
|
4270
|
+
};
|
|
4271
|
+
return () => {
|
|
4272
|
+
const now2 = Date.now();
|
|
4273
|
+
if (now2 - last >= interval) {
|
|
4274
|
+
last = now2;
|
|
4275
|
+
doReload();
|
|
4276
|
+
} else if (!queued) {
|
|
4277
|
+
queued = true;
|
|
4278
|
+
setTimeout(() => {
|
|
4279
|
+
queued = false;
|
|
4280
|
+
last = Date.now();
|
|
4281
|
+
doReload();
|
|
4282
|
+
}, interval - (now2 - last));
|
|
4283
|
+
}
|
|
4284
|
+
};
|
|
4285
|
+
};
|
|
4286
|
+
var throttledReloadDashboard = createThrottledReload(5e3);
|
|
4287
|
+
|
|
4288
|
+
// src/lib/hooks/useHlsStream.ts
|
|
4289
|
+
var HLS_CONFIG = {
|
|
4290
|
+
maxBufferLength: 8,
|
|
4291
|
+
maxMaxBufferLength: 15,
|
|
4292
|
+
lowLatencyMode: false,
|
|
4293
|
+
enableWorker: true,
|
|
4294
|
+
// Retry + timeout tuned for quick recovery
|
|
4295
|
+
manifestLoadingMaxRetry: 4,
|
|
4296
|
+
levelLoadingMaxRetry: 3,
|
|
4297
|
+
fragLoadingMaxRetry: 4,
|
|
4298
|
+
manifestLoadingRetryDelay: 500,
|
|
4299
|
+
levelLoadingRetryDelay: 500,
|
|
4300
|
+
fragLoadingRetryDelay: 500,
|
|
4301
|
+
manifestLoadingTimeOut: 1e4,
|
|
4302
|
+
levelLoadingTimeOut: 8e3,
|
|
4303
|
+
fragLoadingTimeOut: 1e4,
|
|
4304
|
+
liveSyncDurationCount: 2
|
|
4305
|
+
// Follow live edge aggressively
|
|
4306
|
+
};
|
|
4307
|
+
function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
|
|
4308
|
+
const [restartKey, setRestartKey] = React14.useState(0);
|
|
4309
|
+
const hlsRef = React14.useRef(null);
|
|
4310
|
+
const stallCheckIntervalRef = React14.useRef(null);
|
|
4311
|
+
const noProgressTimerRef = React14.useRef(null);
|
|
4312
|
+
const lastTimeUpdateRef = React14.useRef(0);
|
|
4313
|
+
const softRestartCountRef = React14.useRef(0);
|
|
4314
|
+
const isNativeHlsRef = React14.useRef(false);
|
|
4315
|
+
const waitingTimerRef = React14.useRef(null);
|
|
4316
|
+
const cleanup = () => {
|
|
4317
|
+
if (stallCheckIntervalRef.current) {
|
|
4318
|
+
clearInterval(stallCheckIntervalRef.current);
|
|
4319
|
+
stallCheckIntervalRef.current = null;
|
|
4320
|
+
}
|
|
4321
|
+
if (noProgressTimerRef.current) {
|
|
4322
|
+
clearTimeout(noProgressTimerRef.current);
|
|
4323
|
+
noProgressTimerRef.current = null;
|
|
4324
|
+
}
|
|
4325
|
+
if (waitingTimerRef.current) {
|
|
4326
|
+
clearTimeout(waitingTimerRef.current);
|
|
4327
|
+
waitingTimerRef.current = null;
|
|
4328
|
+
}
|
|
4329
|
+
if (hlsRef.current) {
|
|
4330
|
+
hlsRef.current.destroy();
|
|
4331
|
+
hlsRef.current = null;
|
|
4332
|
+
}
|
|
4333
|
+
const video = videoRef.current;
|
|
4334
|
+
if (video) {
|
|
4335
|
+
video.pause();
|
|
4336
|
+
video.removeAttribute("src");
|
|
4337
|
+
video.load();
|
|
4338
|
+
video.removeEventListener("waiting", handleWaiting);
|
|
4339
|
+
video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
4340
|
+
video.removeEventListener("error", handleNativeError);
|
|
4341
|
+
}
|
|
4342
|
+
lastTimeUpdateRef.current = 0;
|
|
4343
|
+
softRestartCountRef.current = 0;
|
|
4344
|
+
};
|
|
4345
|
+
const seekToLiveEdge = () => {
|
|
4346
|
+
const hls = hlsRef.current;
|
|
4347
|
+
const video = videoRef.current;
|
|
4348
|
+
if (!hls || !video) return;
|
|
4349
|
+
if (hls.liveSyncPosition !== null && hls.liveSyncPosition !== void 0) {
|
|
4350
|
+
video.currentTime = hls.liveSyncPosition;
|
|
4351
|
+
} else if (hls.levels?.[hls.currentLevel]?.details) {
|
|
4352
|
+
const levelDetails = hls.levels[hls.currentLevel].details;
|
|
4353
|
+
if (levelDetails && levelDetails.edge !== void 0) {
|
|
4354
|
+
video.currentTime = Math.max(0, levelDetails.edge - 5);
|
|
4355
|
+
}
|
|
4072
4356
|
}
|
|
4073
|
-
|
|
4074
|
-
|
|
4357
|
+
};
|
|
4358
|
+
const softRestart = (reason) => {
|
|
4359
|
+
console.warn(`[HLS] Soft restart: ${reason}`);
|
|
4360
|
+
const hls = hlsRef.current;
|
|
4361
|
+
if (!hls) return;
|
|
4075
4362
|
try {
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
query = query.eq("entity_type", options.entityType);
|
|
4083
|
-
}
|
|
4084
|
-
if (options?.date) {
|
|
4085
|
-
query = query.eq("date", options.date);
|
|
4086
|
-
}
|
|
4087
|
-
if (options?.shiftId !== void 0) {
|
|
4088
|
-
query = query.eq("shift_id", options.shiftId);
|
|
4363
|
+
hls.stopLoad();
|
|
4364
|
+
hls.startLoad(-1);
|
|
4365
|
+
seekToLiveEdge();
|
|
4366
|
+
softRestartCountRef.current++;
|
|
4367
|
+
if (softRestartCountRef.current >= 5) {
|
|
4368
|
+
hardRestart(`${reason} (escalated after ${softRestartCountRef.current} soft restarts)`);
|
|
4089
4369
|
}
|
|
4090
|
-
|
|
4091
|
-
|
|
4370
|
+
} catch (error) {
|
|
4371
|
+
console.error("[HLS] Soft restart failed:", error);
|
|
4372
|
+
hardRestart(`${reason} (soft restart error)`);
|
|
4373
|
+
}
|
|
4374
|
+
};
|
|
4375
|
+
const hardRestart = (reason) => {
|
|
4376
|
+
console.warn(`[HLS] Hard restart: ${reason}`);
|
|
4377
|
+
cleanup();
|
|
4378
|
+
setRestartKey((k) => k + 1);
|
|
4379
|
+
softRestartCountRef.current = 0;
|
|
4380
|
+
if (reason.includes("hard restart") || reason.includes("native video error")) {
|
|
4381
|
+
if (onFatalError) {
|
|
4382
|
+
onFatalError();
|
|
4383
|
+
} else {
|
|
4384
|
+
throttledReloadDashboard();
|
|
4092
4385
|
}
|
|
4093
|
-
query = query.order("createdAt", { ascending: false });
|
|
4094
|
-
const { data, error: fetchError } = await query;
|
|
4095
|
-
if (fetchError) throw fetchError;
|
|
4096
|
-
setTargets(data || []);
|
|
4097
|
-
} catch (err) {
|
|
4098
|
-
console.error("[useTargets] Error fetching targets:", err);
|
|
4099
|
-
setError({ message: err.message, code: err.code || "FETCH_ERROR" });
|
|
4100
|
-
setTargets([]);
|
|
4101
|
-
} finally {
|
|
4102
|
-
setIsLoading(false);
|
|
4103
4386
|
}
|
|
4104
|
-
}, [supabase, companyId, targetsTable, options]);
|
|
4105
|
-
React14.useEffect(() => {
|
|
4106
|
-
fetchData();
|
|
4107
|
-
}, [fetchData]);
|
|
4108
|
-
return {
|
|
4109
|
-
targets,
|
|
4110
|
-
isLoading,
|
|
4111
|
-
error,
|
|
4112
|
-
refetch: fetchData
|
|
4113
4387
|
};
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
const { tables } = useDatabaseConfig();
|
|
4120
|
-
const [shifts, setShifts] = React14.useState([]);
|
|
4121
|
-
const [isLoading, setIsLoading] = React14.useState(true);
|
|
4122
|
-
const [error, setError] = React14.useState(null);
|
|
4123
|
-
const supabase = React14.useMemo(() => {
|
|
4124
|
-
if (!supabaseUrl || !supabaseKey) return null;
|
|
4125
|
-
return supabaseJs.createClient(supabaseUrl, supabaseKey);
|
|
4126
|
-
}, [supabaseUrl, supabaseKey]);
|
|
4127
|
-
const shiftsTable = tables?.shiftConfigurations || DEFAULT_SHIFTS_TABLE_NAME;
|
|
4128
|
-
const fetchData = React14.useCallback(async () => {
|
|
4129
|
-
if (!supabase) {
|
|
4130
|
-
setError({ message: "Supabase client not initialized.", code: "CLIENT_INIT_ERROR" });
|
|
4131
|
-
setIsLoading(false);
|
|
4132
|
-
return;
|
|
4388
|
+
const handleWaiting = () => {
|
|
4389
|
+
if (isNativeHlsRef.current) return;
|
|
4390
|
+
console.log("[HLS] Video waiting (buffer underrun)");
|
|
4391
|
+
if (waitingTimerRef.current) {
|
|
4392
|
+
clearTimeout(waitingTimerRef.current);
|
|
4133
4393
|
}
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4394
|
+
waitingTimerRef.current = setTimeout(() => {
|
|
4395
|
+
const video = videoRef.current;
|
|
4396
|
+
if (video && video.readyState < 3) {
|
|
4397
|
+
softRestart("waiting timeout");
|
|
4398
|
+
}
|
|
4399
|
+
}, 1e4);
|
|
4400
|
+
};
|
|
4401
|
+
const handleTimeUpdate = () => {
|
|
4402
|
+
const video = videoRef.current;
|
|
4403
|
+
if (!video) return;
|
|
4404
|
+
lastTimeUpdateRef.current = video.currentTime;
|
|
4405
|
+
if (waitingTimerRef.current) {
|
|
4406
|
+
clearTimeout(waitingTimerRef.current);
|
|
4407
|
+
waitingTimerRef.current = null;
|
|
4408
|
+
}
|
|
4409
|
+
};
|
|
4410
|
+
const handleNativeError = () => {
|
|
4411
|
+
console.error("[HLS] Native video error");
|
|
4412
|
+
hardRestart("native video error");
|
|
4413
|
+
};
|
|
4414
|
+
const startStallDetection = () => {
|
|
4415
|
+
if (isNativeHlsRef.current) return;
|
|
4416
|
+
stallCheckIntervalRef.current = setInterval(() => {
|
|
4417
|
+
const video = videoRef.current;
|
|
4418
|
+
if (!video || video.paused || video.ended) return;
|
|
4419
|
+
const currentTime = video.currentTime;
|
|
4420
|
+
const lastTime = lastTimeUpdateRef.current;
|
|
4421
|
+
if (Math.abs(currentTime - lastTime) < 0.1 && video.readyState >= 2) {
|
|
4422
|
+
console.warn("[HLS] Playback stall detected");
|
|
4423
|
+
if (!noProgressTimerRef.current) {
|
|
4424
|
+
noProgressTimerRef.current = setTimeout(() => {
|
|
4425
|
+
softRestart("playback stall");
|
|
4426
|
+
noProgressTimerRef.current = null;
|
|
4427
|
+
}, 8e3);
|
|
4428
|
+
}
|
|
4429
|
+
} else {
|
|
4430
|
+
if (noProgressTimerRef.current) {
|
|
4431
|
+
clearTimeout(noProgressTimerRef.current);
|
|
4432
|
+
noProgressTimerRef.current = null;
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
}, 7e3);
|
|
4436
|
+
};
|
|
4437
|
+
React14.useEffect(() => {
|
|
4438
|
+
if (!src || !shouldPlay) {
|
|
4439
|
+
cleanup();
|
|
4138
4440
|
return;
|
|
4139
4441
|
}
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4442
|
+
const video = videoRef.current;
|
|
4443
|
+
if (!video) return;
|
|
4444
|
+
isNativeHlsRef.current = video.canPlayType("application/vnd.apple.mpegurl") === "probably";
|
|
4445
|
+
if (Hls2__default.default.isSupported() && !isNativeHlsRef.current) {
|
|
4446
|
+
const hls = new Hls2__default.default(HLS_CONFIG);
|
|
4447
|
+
hlsRef.current = hls;
|
|
4448
|
+
hls.attachMedia(video);
|
|
4449
|
+
hls.loadSource(src);
|
|
4450
|
+
hls.on(Hls2__default.default.Events.ERROR, (_, data) => {
|
|
4451
|
+
if (!data.fatal) return;
|
|
4452
|
+
console.error("[HLS] Fatal error:", data.type, data.details);
|
|
4453
|
+
if (data.response?.code === 404) {
|
|
4454
|
+
hardRestart("404 hard restart");
|
|
4455
|
+
return;
|
|
4456
|
+
}
|
|
4457
|
+
switch (data.type) {
|
|
4458
|
+
case Hls2__default.default.ErrorTypes.NETWORK_ERROR:
|
|
4459
|
+
case Hls2__default.default.ErrorTypes.MEDIA_ERROR:
|
|
4460
|
+
softRestart(`${data.type}: ${data.details}`);
|
|
4461
|
+
break;
|
|
4462
|
+
default:
|
|
4463
|
+
hardRestart(`Fatal ${data.type}: ${data.details}`);
|
|
4464
|
+
break;
|
|
4465
|
+
}
|
|
4466
|
+
});
|
|
4467
|
+
hls.on(Hls2__default.default.Events.MANIFEST_PARSED, () => {
|
|
4468
|
+
video.play().catch((err) => {
|
|
4469
|
+
console.error("[HLS] Play failed:", err);
|
|
4470
|
+
});
|
|
4471
|
+
});
|
|
4472
|
+
video.addEventListener("waiting", handleWaiting);
|
|
4473
|
+
video.addEventListener("timeupdate", handleTimeUpdate);
|
|
4474
|
+
startStallDetection();
|
|
4475
|
+
} else if (isNativeHlsRef.current) {
|
|
4476
|
+
console.log("[HLS] Using native HLS");
|
|
4477
|
+
video.src = src;
|
|
4478
|
+
video.addEventListener("error", handleNativeError);
|
|
4479
|
+
video.play().catch((err) => {
|
|
4480
|
+
console.error("[HLS] Native play failed:", err);
|
|
4481
|
+
});
|
|
4482
|
+
} else {
|
|
4483
|
+
console.error("[HLS] HLS not supported");
|
|
4155
4484
|
}
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
fetchData();
|
|
4159
|
-
}, [fetchData]);
|
|
4485
|
+
return cleanup;
|
|
4486
|
+
}, [src, shouldPlay, restartKey, onFatalError]);
|
|
4160
4487
|
return {
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
error,
|
|
4164
|
-
refetch: fetchData
|
|
4488
|
+
restartKey,
|
|
4489
|
+
isNativeHls: isNativeHlsRef.current
|
|
4165
4490
|
};
|
|
4166
|
-
}
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
const
|
|
4170
|
-
const
|
|
4171
|
-
const
|
|
4172
|
-
const
|
|
4173
|
-
const
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4491
|
+
}
|
|
4492
|
+
function useHlsStreamWithCropping(videoRef, canvasRef, options) {
|
|
4493
|
+
const { src, shouldPlay, cropping, canvasFps = 30, useRAF = true, onFatalError } = options;
|
|
4494
|
+
const animationFrameRef = React14.useRef(null);
|
|
4495
|
+
const intervalRef = React14.useRef(null);
|
|
4496
|
+
const isDrawingRef = React14.useRef(false);
|
|
4497
|
+
const hlsState = useHlsStream(videoRef, { src, shouldPlay, onFatalError });
|
|
4498
|
+
const calculateCropRect = React14.useCallback((video, cropping2) => {
|
|
4499
|
+
const videoWidth = video.videoWidth;
|
|
4500
|
+
const videoHeight = video.videoHeight;
|
|
4501
|
+
const sx = cropping2.x / 100 * videoWidth;
|
|
4502
|
+
const sy = cropping2.y / 100 * videoHeight;
|
|
4503
|
+
const sw = cropping2.width / 100 * videoWidth;
|
|
4504
|
+
const sh = cropping2.height / 100 * videoHeight;
|
|
4505
|
+
return { sx, sy, sw, sh };
|
|
4506
|
+
}, []);
|
|
4507
|
+
const drawFrame = React14.useCallback(() => {
|
|
4508
|
+
const video = videoRef.current;
|
|
4509
|
+
const canvas = canvasRef.current;
|
|
4510
|
+
if (!video || !canvas || !cropping) return;
|
|
4511
|
+
const ctx = canvas.getContext("2d");
|
|
4512
|
+
if (!ctx) return;
|
|
4513
|
+
if (video.readyState < 2) return;
|
|
4514
|
+
try {
|
|
4515
|
+
const videoWidth = video.videoWidth;
|
|
4516
|
+
const videoHeight = video.videoHeight;
|
|
4517
|
+
if (!videoWidth || !videoHeight) return;
|
|
4518
|
+
const { sx, sy, sw, sh } = calculateCropRect(video, cropping);
|
|
4519
|
+
const canvasContainer = canvas.parentElement;
|
|
4520
|
+
if (canvasContainer) {
|
|
4521
|
+
const containerWidth = canvasContainer.clientWidth;
|
|
4522
|
+
const containerHeight = canvasContainer.clientHeight;
|
|
4523
|
+
if (canvas.width !== containerWidth || canvas.height !== containerHeight) {
|
|
4524
|
+
canvas.width = containerWidth;
|
|
4525
|
+
canvas.height = containerHeight;
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
4529
|
+
ctx.drawImage(
|
|
4530
|
+
video,
|
|
4531
|
+
sx,
|
|
4532
|
+
sy,
|
|
4533
|
+
sw,
|
|
4534
|
+
sh,
|
|
4535
|
+
// Source rectangle (cropped portion)
|
|
4536
|
+
0,
|
|
4537
|
+
0,
|
|
4538
|
+
canvas.width,
|
|
4539
|
+
canvas.height
|
|
4540
|
+
// Destination rectangle (full canvas)
|
|
4541
|
+
);
|
|
4542
|
+
} catch (err) {
|
|
4543
|
+
console.warn("Canvas drawing error:", err);
|
|
4544
|
+
}
|
|
4545
|
+
}, [videoRef, canvasRef, cropping, calculateCropRect]);
|
|
4546
|
+
const startCanvasRendering = React14.useCallback(() => {
|
|
4547
|
+
if (isDrawingRef.current) return;
|
|
4548
|
+
isDrawingRef.current = true;
|
|
4549
|
+
if (useRAF) {
|
|
4550
|
+
const animate = () => {
|
|
4551
|
+
drawFrame();
|
|
4552
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
4553
|
+
};
|
|
4554
|
+
animate();
|
|
4555
|
+
} else {
|
|
4556
|
+
const frameInterval = 1e3 / canvasFps;
|
|
4557
|
+
intervalRef.current = setInterval(drawFrame, frameInterval);
|
|
4181
4558
|
}
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4559
|
+
}, [drawFrame, canvasFps, useRAF]);
|
|
4560
|
+
const stopCanvasRendering = React14.useCallback(() => {
|
|
4561
|
+
isDrawingRef.current = false;
|
|
4562
|
+
if (animationFrameRef.current) {
|
|
4563
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
4564
|
+
animationFrameRef.current = null;
|
|
4186
4565
|
}
|
|
4187
|
-
if (
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4566
|
+
if (intervalRef.current) {
|
|
4567
|
+
clearInterval(intervalRef.current);
|
|
4568
|
+
intervalRef.current = null;
|
|
4569
|
+
}
|
|
4570
|
+
}, []);
|
|
4571
|
+
React14.useEffect(() => {
|
|
4572
|
+
const video = videoRef.current;
|
|
4573
|
+
if (!video || !cropping || !shouldPlay) {
|
|
4574
|
+
stopCanvasRendering();
|
|
4191
4575
|
return;
|
|
4192
4576
|
}
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
let query = supabase.from(operatorsTable).select("*");
|
|
4197
|
-
query = query.eq("company_id", companyId);
|
|
4198
|
-
query = query.eq("workspace_id", workspaceId);
|
|
4199
|
-
if (options?.date) {
|
|
4200
|
-
query = query.eq("date", options.date);
|
|
4201
|
-
}
|
|
4202
|
-
if (options?.shiftId !== void 0) {
|
|
4203
|
-
query = query.eq("shift_id", options.shiftId);
|
|
4577
|
+
const handlePlay = () => {
|
|
4578
|
+
if (cropping) {
|
|
4579
|
+
startCanvasRendering();
|
|
4204
4580
|
}
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4581
|
+
};
|
|
4582
|
+
const handlePause = () => {
|
|
4583
|
+
stopCanvasRendering();
|
|
4584
|
+
};
|
|
4585
|
+
const handleEnded = () => {
|
|
4586
|
+
stopCanvasRendering();
|
|
4587
|
+
};
|
|
4588
|
+
video.addEventListener("play", handlePlay);
|
|
4589
|
+
video.addEventListener("pause", handlePause);
|
|
4590
|
+
video.addEventListener("ended", handleEnded);
|
|
4591
|
+
if (!video.paused && video.readyState >= 2) {
|
|
4592
|
+
startCanvasRendering();
|
|
4215
4593
|
}
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4594
|
+
return () => {
|
|
4595
|
+
stopCanvasRendering();
|
|
4596
|
+
video.removeEventListener("play", handlePlay);
|
|
4597
|
+
video.removeEventListener("pause", handlePause);
|
|
4598
|
+
video.removeEventListener("ended", handleEnded);
|
|
4599
|
+
};
|
|
4600
|
+
}, [videoRef, cropping, shouldPlay, startCanvasRendering, stopCanvasRendering]);
|
|
4220
4601
|
return {
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
error,
|
|
4224
|
-
refetch: fetchData
|
|
4602
|
+
...hlsState,
|
|
4603
|
+
isCanvasRendering: isDrawingRef.current
|
|
4225
4604
|
};
|
|
4226
|
-
}
|
|
4605
|
+
}
|
|
4227
4606
|
function useThreads() {
|
|
4228
4607
|
const supabase = _getSupabaseInstance();
|
|
4229
4608
|
const fetcher = async (key) => {
|
|
@@ -4883,347 +5262,116 @@ function useNavigation(customNavigate) {
|
|
|
4883
5262
|
},
|
|
4884
5263
|
[router$1, customNavigate]
|
|
4885
5264
|
);
|
|
4886
|
-
return {
|
|
4887
|
-
pathname,
|
|
4888
|
-
query,
|
|
4889
|
-
isReady,
|
|
4890
|
-
activeLineId,
|
|
4891
|
-
activeWorkspaceId,
|
|
4892
|
-
isActive,
|
|
4893
|
-
isInSection,
|
|
4894
|
-
isLineView,
|
|
4895
|
-
isWorkspaceView,
|
|
4896
|
-
goToDashboard,
|
|
4897
|
-
goToWorkspace,
|
|
4898
|
-
goToLine,
|
|
4899
|
-
goToTargets,
|
|
4900
|
-
goToShifts,
|
|
4901
|
-
goToLeaderboard,
|
|
4902
|
-
goToFactoryView,
|
|
4903
|
-
goToProfile,
|
|
4904
|
-
navigate
|
|
4905
|
-
};
|
|
4906
|
-
}
|
|
4907
|
-
function useWorkspaceNavigation() {
|
|
4908
|
-
const { defaultTimezone } = useDateTimeConfig();
|
|
4909
|
-
const getWorkspaceNavigationParams3 = React14.useCallback(
|
|
4910
|
-
(workspaceId, options) => {
|
|
4911
|
-
let dateToUse = options?.date;
|
|
4912
|
-
if (!dateToUse && options?.useCurrentDate) {
|
|
4913
|
-
dateToUse = getOperationalDate(defaultTimezone || "UTC");
|
|
4914
|
-
}
|
|
4915
|
-
return {
|
|
4916
|
-
workspaceId,
|
|
4917
|
-
date: dateToUse,
|
|
4918
|
-
shift: options?.shift,
|
|
4919
|
-
sourceType: options?.sourceType
|
|
4920
|
-
};
|
|
4921
|
-
},
|
|
4922
|
-
[defaultTimezone]
|
|
4923
|
-
);
|
|
4924
|
-
return {
|
|
4925
|
-
getWorkspaceNavigationParams: getWorkspaceNavigationParams3
|
|
4926
|
-
};
|
|
4927
|
-
}
|
|
4928
|
-
function useDateFormatter() {
|
|
4929
|
-
const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
|
|
4930
|
-
const formatDate = React14.useCallback(
|
|
4931
|
-
(date, formatString) => {
|
|
4932
|
-
const dateObj = typeof date === "string" ? dateFns.parseISO(date) : date;
|
|
4933
|
-
if (!dateFns.isValid(dateObj)) return "Invalid Date";
|
|
4934
|
-
const tz = defaultTimezone || "UTC";
|
|
4935
|
-
if (formatString) {
|
|
4936
|
-
return dateFnsTz.formatInTimeZone(dateObj, tz, formatString);
|
|
4937
|
-
}
|
|
4938
|
-
const effectiveOptions = dateFormatOptions || { year: "numeric", month: "short", day: "numeric" };
|
|
4939
|
-
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
4940
|
-
},
|
|
4941
|
-
[defaultTimezone, defaultLocale, dateFormatOptions]
|
|
4942
|
-
);
|
|
4943
|
-
const formatTime2 = React14.useCallback(
|
|
4944
|
-
(date, formatString) => {
|
|
4945
|
-
const dateObj = typeof date === "string" ? dateFns.parseISO(date) : date;
|
|
4946
|
-
if (!dateFns.isValid(dateObj)) return "Invalid Time";
|
|
4947
|
-
const tz = defaultTimezone || "UTC";
|
|
4948
|
-
if (formatString) {
|
|
4949
|
-
return dateFnsTz.formatInTimeZone(dateObj, tz, formatString);
|
|
4950
|
-
}
|
|
4951
|
-
const effectiveOptions = timeFormatOptions || { hour: "numeric", minute: "numeric" };
|
|
4952
|
-
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
4953
|
-
},
|
|
4954
|
-
[defaultTimezone, defaultLocale, timeFormatOptions]
|
|
4955
|
-
);
|
|
4956
|
-
const formatDateTime = React14.useCallback(
|
|
4957
|
-
(date, formatString) => {
|
|
4958
|
-
const dateObj = typeof date === "string" ? dateFns.parseISO(date) : date;
|
|
4959
|
-
if (!dateFns.isValid(dateObj)) return "Invalid Date/Time";
|
|
4960
|
-
const tz = defaultTimezone || "UTC";
|
|
4961
|
-
if (formatString) {
|
|
4962
|
-
return dateFnsTz.formatInTimeZone(dateObj, tz, formatString);
|
|
4963
|
-
}
|
|
4964
|
-
const effectiveOptions = dateTimeFormatOptions || { year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "numeric" };
|
|
4965
|
-
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
4966
|
-
},
|
|
4967
|
-
[defaultTimezone, defaultLocale, dateTimeFormatOptions]
|
|
4968
|
-
);
|
|
4969
|
-
const getNow = React14.useCallback(() => {
|
|
4970
|
-
return /* @__PURE__ */ new Date();
|
|
4971
|
-
}, []);
|
|
4972
|
-
return {
|
|
4973
|
-
formatDate,
|
|
4974
|
-
formatTime: formatTime2,
|
|
4975
|
-
formatDateTime,
|
|
4976
|
-
getNow,
|
|
4977
|
-
timezone: defaultTimezone || "UTC",
|
|
4978
|
-
locale: defaultLocale || "en-US"
|
|
4979
|
-
};
|
|
4980
|
-
}
|
|
4981
|
-
var useFormatNumber = () => {
|
|
4982
|
-
const { defaultLocale } = useDateTimeConfig();
|
|
4983
|
-
const formatNumber = React14.useCallback(
|
|
4984
|
-
(value, options) => {
|
|
4985
|
-
try {
|
|
4986
|
-
return new Intl.NumberFormat(defaultLocale || "en-US", options).format(value);
|
|
4987
|
-
} catch (error) {
|
|
4988
|
-
console.error("Error formatting number:", error);
|
|
4989
|
-
return String(value);
|
|
4990
|
-
}
|
|
4991
|
-
},
|
|
4992
|
-
[defaultLocale]
|
|
4993
|
-
);
|
|
4994
|
-
return { formatNumber };
|
|
4995
|
-
};
|
|
4996
|
-
|
|
4997
|
-
// src/lib/utils/dashboardReload.ts
|
|
4998
|
-
var createThrottledReload = (interval = 5e3) => {
|
|
4999
|
-
let last = 0;
|
|
5000
|
-
let queued = false;
|
|
5001
|
-
const doReload = () => {
|
|
5002
|
-
if (typeof window !== "undefined") {
|
|
5003
|
-
window.location.reload();
|
|
5004
|
-
}
|
|
5005
|
-
};
|
|
5006
|
-
return () => {
|
|
5007
|
-
const now2 = Date.now();
|
|
5008
|
-
if (now2 - last >= interval) {
|
|
5009
|
-
last = now2;
|
|
5010
|
-
doReload();
|
|
5011
|
-
} else if (!queued) {
|
|
5012
|
-
queued = true;
|
|
5013
|
-
setTimeout(() => {
|
|
5014
|
-
queued = false;
|
|
5015
|
-
last = Date.now();
|
|
5016
|
-
doReload();
|
|
5017
|
-
}, interval - (now2 - last));
|
|
5018
|
-
}
|
|
5019
|
-
};
|
|
5020
|
-
};
|
|
5021
|
-
var throttledReloadDashboard = createThrottledReload(5e3);
|
|
5022
|
-
|
|
5023
|
-
// src/lib/hooks/useHlsStream.ts
|
|
5024
|
-
var HLS_CONFIG = {
|
|
5025
|
-
maxBufferLength: 8,
|
|
5026
|
-
maxMaxBufferLength: 15,
|
|
5027
|
-
lowLatencyMode: false,
|
|
5028
|
-
enableWorker: true,
|
|
5029
|
-
// Retry + timeout tuned for quick recovery
|
|
5030
|
-
manifestLoadingMaxRetry: 4,
|
|
5031
|
-
levelLoadingMaxRetry: 3,
|
|
5032
|
-
fragLoadingMaxRetry: 4,
|
|
5033
|
-
manifestLoadingRetryDelay: 500,
|
|
5034
|
-
levelLoadingRetryDelay: 500,
|
|
5035
|
-
fragLoadingRetryDelay: 500,
|
|
5036
|
-
manifestLoadingTimeOut: 1e4,
|
|
5037
|
-
levelLoadingTimeOut: 8e3,
|
|
5038
|
-
fragLoadingTimeOut: 1e4,
|
|
5039
|
-
liveSyncDurationCount: 2
|
|
5040
|
-
// Follow live edge aggressively
|
|
5041
|
-
};
|
|
5042
|
-
function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
|
|
5043
|
-
const [restartKey, setRestartKey] = React14.useState(0);
|
|
5044
|
-
const hlsRef = React14.useRef(null);
|
|
5045
|
-
const stallCheckIntervalRef = React14.useRef(null);
|
|
5046
|
-
const noProgressTimerRef = React14.useRef(null);
|
|
5047
|
-
const lastTimeUpdateRef = React14.useRef(0);
|
|
5048
|
-
const softRestartCountRef = React14.useRef(0);
|
|
5049
|
-
const isNativeHlsRef = React14.useRef(false);
|
|
5050
|
-
const waitingTimerRef = React14.useRef(null);
|
|
5051
|
-
const cleanup = () => {
|
|
5052
|
-
if (stallCheckIntervalRef.current) {
|
|
5053
|
-
clearInterval(stallCheckIntervalRef.current);
|
|
5054
|
-
stallCheckIntervalRef.current = null;
|
|
5055
|
-
}
|
|
5056
|
-
if (noProgressTimerRef.current) {
|
|
5057
|
-
clearTimeout(noProgressTimerRef.current);
|
|
5058
|
-
noProgressTimerRef.current = null;
|
|
5059
|
-
}
|
|
5060
|
-
if (waitingTimerRef.current) {
|
|
5061
|
-
clearTimeout(waitingTimerRef.current);
|
|
5062
|
-
waitingTimerRef.current = null;
|
|
5063
|
-
}
|
|
5064
|
-
if (hlsRef.current) {
|
|
5065
|
-
hlsRef.current.destroy();
|
|
5066
|
-
hlsRef.current = null;
|
|
5067
|
-
}
|
|
5068
|
-
const video = videoRef.current;
|
|
5069
|
-
if (video) {
|
|
5070
|
-
video.pause();
|
|
5071
|
-
video.removeAttribute("src");
|
|
5072
|
-
video.load();
|
|
5073
|
-
video.removeEventListener("waiting", handleWaiting);
|
|
5074
|
-
video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
5075
|
-
video.removeEventListener("error", handleNativeError);
|
|
5076
|
-
}
|
|
5077
|
-
lastTimeUpdateRef.current = 0;
|
|
5078
|
-
softRestartCountRef.current = 0;
|
|
5079
|
-
};
|
|
5080
|
-
const seekToLiveEdge = () => {
|
|
5081
|
-
const hls = hlsRef.current;
|
|
5082
|
-
const video = videoRef.current;
|
|
5083
|
-
if (!hls || !video) return;
|
|
5084
|
-
if (hls.liveSyncPosition !== null && hls.liveSyncPosition !== void 0) {
|
|
5085
|
-
video.currentTime = hls.liveSyncPosition;
|
|
5086
|
-
} else if (hls.levels?.[hls.currentLevel]?.details) {
|
|
5087
|
-
const levelDetails = hls.levels[hls.currentLevel].details;
|
|
5088
|
-
if (levelDetails && levelDetails.edge !== void 0) {
|
|
5089
|
-
video.currentTime = Math.max(0, levelDetails.edge - 5);
|
|
5090
|
-
}
|
|
5091
|
-
}
|
|
5265
|
+
return {
|
|
5266
|
+
pathname,
|
|
5267
|
+
query,
|
|
5268
|
+
isReady,
|
|
5269
|
+
activeLineId,
|
|
5270
|
+
activeWorkspaceId,
|
|
5271
|
+
isActive,
|
|
5272
|
+
isInSection,
|
|
5273
|
+
isLineView,
|
|
5274
|
+
isWorkspaceView,
|
|
5275
|
+
goToDashboard,
|
|
5276
|
+
goToWorkspace,
|
|
5277
|
+
goToLine,
|
|
5278
|
+
goToTargets,
|
|
5279
|
+
goToShifts,
|
|
5280
|
+
goToLeaderboard,
|
|
5281
|
+
goToFactoryView,
|
|
5282
|
+
goToProfile,
|
|
5283
|
+
navigate
|
|
5092
5284
|
};
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
softRestartCountRef.current++;
|
|
5102
|
-
if (softRestartCountRef.current >= 5) {
|
|
5103
|
-
hardRestart(`${reason} (escalated after ${softRestartCountRef.current} soft restarts)`);
|
|
5285
|
+
}
|
|
5286
|
+
function useWorkspaceNavigation() {
|
|
5287
|
+
const { defaultTimezone } = useDateTimeConfig();
|
|
5288
|
+
const getWorkspaceNavigationParams3 = React14.useCallback(
|
|
5289
|
+
(workspaceId, options) => {
|
|
5290
|
+
let dateToUse = options?.date;
|
|
5291
|
+
if (!dateToUse && options?.useCurrentDate) {
|
|
5292
|
+
dateToUse = getOperationalDate(defaultTimezone || "UTC");
|
|
5104
5293
|
}
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5294
|
+
return {
|
|
5295
|
+
workspaceId,
|
|
5296
|
+
date: dateToUse,
|
|
5297
|
+
shift: options?.shift,
|
|
5298
|
+
sourceType: options?.sourceType
|
|
5299
|
+
};
|
|
5300
|
+
},
|
|
5301
|
+
[defaultTimezone]
|
|
5302
|
+
);
|
|
5303
|
+
return {
|
|
5304
|
+
getWorkspaceNavigationParams: getWorkspaceNavigationParams3
|
|
5109
5305
|
};
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
if (
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5306
|
+
}
|
|
5307
|
+
function useDateFormatter() {
|
|
5308
|
+
const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
|
|
5309
|
+
const formatDate = React14.useCallback(
|
|
5310
|
+
(date, formatString) => {
|
|
5311
|
+
const dateObj = typeof date === "string" ? dateFns.parseISO(date) : date;
|
|
5312
|
+
if (!dateFns.isValid(dateObj)) return "Invalid Date";
|
|
5313
|
+
const tz = defaultTimezone || "UTC";
|
|
5314
|
+
if (formatString) {
|
|
5315
|
+
return dateFnsTz.formatInTimeZone(dateObj, tz, formatString);
|
|
5120
5316
|
}
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
const
|
|
5131
|
-
if (
|
|
5132
|
-
|
|
5317
|
+
const effectiveOptions = dateFormatOptions || { year: "numeric", month: "short", day: "numeric" };
|
|
5318
|
+
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
5319
|
+
},
|
|
5320
|
+
[defaultTimezone, defaultLocale, dateFormatOptions]
|
|
5321
|
+
);
|
|
5322
|
+
const formatTime2 = React14.useCallback(
|
|
5323
|
+
(date, formatString) => {
|
|
5324
|
+
const dateObj = typeof date === "string" ? dateFns.parseISO(date) : date;
|
|
5325
|
+
if (!dateFns.isValid(dateObj)) return "Invalid Time";
|
|
5326
|
+
const tz = defaultTimezone || "UTC";
|
|
5327
|
+
if (formatString) {
|
|
5328
|
+
return dateFnsTz.formatInTimeZone(dateObj, tz, formatString);
|
|
5133
5329
|
}
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
console.error("[HLS] Native video error");
|
|
5147
|
-
hardRestart("native video error");
|
|
5148
|
-
};
|
|
5149
|
-
const startStallDetection = () => {
|
|
5150
|
-
if (isNativeHlsRef.current) return;
|
|
5151
|
-
stallCheckIntervalRef.current = setInterval(() => {
|
|
5152
|
-
const video = videoRef.current;
|
|
5153
|
-
if (!video || video.paused || video.ended) return;
|
|
5154
|
-
const currentTime = video.currentTime;
|
|
5155
|
-
const lastTime = lastTimeUpdateRef.current;
|
|
5156
|
-
if (Math.abs(currentTime - lastTime) < 0.1 && video.readyState >= 2) {
|
|
5157
|
-
console.warn("[HLS] Playback stall detected");
|
|
5158
|
-
if (!noProgressTimerRef.current) {
|
|
5159
|
-
noProgressTimerRef.current = setTimeout(() => {
|
|
5160
|
-
softRestart("playback stall");
|
|
5161
|
-
noProgressTimerRef.current = null;
|
|
5162
|
-
}, 8e3);
|
|
5163
|
-
}
|
|
5164
|
-
} else {
|
|
5165
|
-
if (noProgressTimerRef.current) {
|
|
5166
|
-
clearTimeout(noProgressTimerRef.current);
|
|
5167
|
-
noProgressTimerRef.current = null;
|
|
5168
|
-
}
|
|
5330
|
+
const effectiveOptions = timeFormatOptions || { hour: "numeric", minute: "numeric" };
|
|
5331
|
+
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
5332
|
+
},
|
|
5333
|
+
[defaultTimezone, defaultLocale, timeFormatOptions]
|
|
5334
|
+
);
|
|
5335
|
+
const formatDateTime = React14.useCallback(
|
|
5336
|
+
(date, formatString) => {
|
|
5337
|
+
const dateObj = typeof date === "string" ? dateFns.parseISO(date) : date;
|
|
5338
|
+
if (!dateFns.isValid(dateObj)) return "Invalid Date/Time";
|
|
5339
|
+
const tz = defaultTimezone || "UTC";
|
|
5340
|
+
if (formatString) {
|
|
5341
|
+
return dateFnsTz.formatInTimeZone(dateObj, tz, formatString);
|
|
5169
5342
|
}
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
if (!video) return;
|
|
5179
|
-
isNativeHlsRef.current = video.canPlayType("application/vnd.apple.mpegurl") === "probably";
|
|
5180
|
-
if (Hls2__default.default.isSupported() && !isNativeHlsRef.current) {
|
|
5181
|
-
const hls = new Hls2__default.default(HLS_CONFIG);
|
|
5182
|
-
hlsRef.current = hls;
|
|
5183
|
-
hls.attachMedia(video);
|
|
5184
|
-
hls.loadSource(src);
|
|
5185
|
-
hls.on(Hls2__default.default.Events.ERROR, (_, data) => {
|
|
5186
|
-
if (!data.fatal) return;
|
|
5187
|
-
console.error("[HLS] Fatal error:", data.type, data.details);
|
|
5188
|
-
if (data.response?.code === 404) {
|
|
5189
|
-
hardRestart("404 hard restart");
|
|
5190
|
-
return;
|
|
5191
|
-
}
|
|
5192
|
-
switch (data.type) {
|
|
5193
|
-
case Hls2__default.default.ErrorTypes.NETWORK_ERROR:
|
|
5194
|
-
case Hls2__default.default.ErrorTypes.MEDIA_ERROR:
|
|
5195
|
-
softRestart(`${data.type}: ${data.details}`);
|
|
5196
|
-
break;
|
|
5197
|
-
default:
|
|
5198
|
-
hardRestart(`Fatal ${data.type}: ${data.details}`);
|
|
5199
|
-
break;
|
|
5200
|
-
}
|
|
5201
|
-
});
|
|
5202
|
-
hls.on(Hls2__default.default.Events.MANIFEST_PARSED, () => {
|
|
5203
|
-
video.play().catch((err) => {
|
|
5204
|
-
console.error("[HLS] Play failed:", err);
|
|
5205
|
-
});
|
|
5206
|
-
});
|
|
5207
|
-
video.addEventListener("waiting", handleWaiting);
|
|
5208
|
-
video.addEventListener("timeupdate", handleTimeUpdate);
|
|
5209
|
-
startStallDetection();
|
|
5210
|
-
} else if (isNativeHlsRef.current) {
|
|
5211
|
-
console.log("[HLS] Using native HLS");
|
|
5212
|
-
video.src = src;
|
|
5213
|
-
video.addEventListener("error", handleNativeError);
|
|
5214
|
-
video.play().catch((err) => {
|
|
5215
|
-
console.error("[HLS] Native play failed:", err);
|
|
5216
|
-
});
|
|
5217
|
-
} else {
|
|
5218
|
-
console.error("[HLS] HLS not supported");
|
|
5219
|
-
}
|
|
5220
|
-
return cleanup;
|
|
5221
|
-
}, [src, shouldPlay, restartKey, onFatalError]);
|
|
5343
|
+
const effectiveOptions = dateTimeFormatOptions || { year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "numeric" };
|
|
5344
|
+
return new Intl.DateTimeFormat(defaultLocale || "en-US", effectiveOptions).format(dateObj);
|
|
5345
|
+
},
|
|
5346
|
+
[defaultTimezone, defaultLocale, dateTimeFormatOptions]
|
|
5347
|
+
);
|
|
5348
|
+
const getNow = React14.useCallback(() => {
|
|
5349
|
+
return /* @__PURE__ */ new Date();
|
|
5350
|
+
}, []);
|
|
5222
5351
|
return {
|
|
5223
|
-
|
|
5224
|
-
|
|
5352
|
+
formatDate,
|
|
5353
|
+
formatTime: formatTime2,
|
|
5354
|
+
formatDateTime,
|
|
5355
|
+
getNow,
|
|
5356
|
+
timezone: defaultTimezone || "UTC",
|
|
5357
|
+
locale: defaultLocale || "en-US"
|
|
5225
5358
|
};
|
|
5226
5359
|
}
|
|
5360
|
+
var useFormatNumber = () => {
|
|
5361
|
+
const { defaultLocale } = useDateTimeConfig();
|
|
5362
|
+
const formatNumber = React14.useCallback(
|
|
5363
|
+
(value, options) => {
|
|
5364
|
+
try {
|
|
5365
|
+
return new Intl.NumberFormat(defaultLocale || "en-US", options).format(value);
|
|
5366
|
+
} catch (error) {
|
|
5367
|
+
console.error("Error formatting number:", error);
|
|
5368
|
+
return String(value);
|
|
5369
|
+
}
|
|
5370
|
+
},
|
|
5371
|
+
[defaultLocale]
|
|
5372
|
+
);
|
|
5373
|
+
return { formatNumber };
|
|
5374
|
+
};
|
|
5227
5375
|
|
|
5228
5376
|
// src/lib/utils/api.ts
|
|
5229
5377
|
var apiUtils = {
|
|
@@ -17165,15 +17313,15 @@ var HourlyOutputChart = ({
|
|
|
17165
17313
|
renderLegend()
|
|
17166
17314
|
] });
|
|
17167
17315
|
};
|
|
17168
|
-
|
|
17169
|
-
|
|
17170
|
-
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
}
|
|
17176
|
-
|
|
17316
|
+
function getTrendArrowAndColor(trend) {
|
|
17317
|
+
if (trend > 0) {
|
|
17318
|
+
return { arrow: "\u2191", color: "text-green-400" };
|
|
17319
|
+
} else if (trend < 0) {
|
|
17320
|
+
return { arrow: "\u2193", color: "text-red-400" };
|
|
17321
|
+
} else {
|
|
17322
|
+
return { arrow: "\u2192", color: "text-gray-400" };
|
|
17323
|
+
}
|
|
17324
|
+
}
|
|
17177
17325
|
var VideoCard = React14__namespace.default.memo(({
|
|
17178
17326
|
workspace,
|
|
17179
17327
|
hlsUrl,
|
|
@@ -17181,14 +17329,29 @@ var VideoCard = React14__namespace.default.memo(({
|
|
|
17181
17329
|
onClick,
|
|
17182
17330
|
onFatalError,
|
|
17183
17331
|
isVeryLowEfficiency = false,
|
|
17332
|
+
cropping,
|
|
17333
|
+
canvasFps = 30,
|
|
17334
|
+
useRAF = true,
|
|
17184
17335
|
className = ""
|
|
17185
17336
|
}) => {
|
|
17186
17337
|
const videoRef = React14.useRef(null);
|
|
17187
|
-
|
|
17188
|
-
|
|
17189
|
-
|
|
17190
|
-
|
|
17191
|
-
|
|
17338
|
+
const canvasRef = React14.useRef(null);
|
|
17339
|
+
if (cropping) {
|
|
17340
|
+
useHlsStreamWithCropping(videoRef, canvasRef, {
|
|
17341
|
+
src: hlsUrl,
|
|
17342
|
+
shouldPlay,
|
|
17343
|
+
cropping,
|
|
17344
|
+
canvasFps,
|
|
17345
|
+
useRAF,
|
|
17346
|
+
onFatalError: onFatalError ?? (() => throttledReloadDashboard())
|
|
17347
|
+
});
|
|
17348
|
+
} else {
|
|
17349
|
+
useHlsStream(videoRef, {
|
|
17350
|
+
src: hlsUrl,
|
|
17351
|
+
shouldPlay,
|
|
17352
|
+
onFatalError: onFatalError ?? (() => throttledReloadDashboard())
|
|
17353
|
+
});
|
|
17354
|
+
}
|
|
17192
17355
|
const displayName = getWorkspaceDisplayName(workspace.workspace_name);
|
|
17193
17356
|
workspace.workspace_uuid || workspace.workspace_name;
|
|
17194
17357
|
const getEfficiencyOverlayColor = (efficiency) => {
|
|
@@ -17243,17 +17406,26 @@ var VideoCard = React14__namespace.default.memo(({
|
|
|
17243
17406
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Camera, { className: "w-6 h-6 text-gray-500" }),
|
|
17244
17407
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500 mt-1", children: "Loading..." })
|
|
17245
17408
|
] }) }),
|
|
17246
|
-
/* @__PURE__ */ jsxRuntime.
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
|
|
17409
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute inset-0 z-10", children: [
|
|
17410
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
17411
|
+
"video",
|
|
17412
|
+
{
|
|
17413
|
+
ref: videoRef,
|
|
17414
|
+
className: `h-full w-full object-cover ${cropping ? "hidden" : ""}`,
|
|
17415
|
+
playsInline: true,
|
|
17416
|
+
muted: true,
|
|
17417
|
+
disablePictureInPicture: true,
|
|
17418
|
+
controlsList: "nodownload noplaybackrate"
|
|
17419
|
+
}
|
|
17420
|
+
),
|
|
17421
|
+
cropping && /* @__PURE__ */ jsxRuntime.jsx(
|
|
17422
|
+
"canvas",
|
|
17423
|
+
{
|
|
17424
|
+
ref: canvasRef,
|
|
17425
|
+
className: "h-full w-full object-cover"
|
|
17426
|
+
}
|
|
17427
|
+
)
|
|
17428
|
+
] }),
|
|
17257
17429
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute inset-0 z-20 pointer-events-none ${efficiencyOverlayClass}` }),
|
|
17258
17430
|
/* @__PURE__ */ jsxRuntime.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: [
|
|
17259
17431
|
Math.round(workspace.efficiency),
|
|
@@ -17289,7 +17461,7 @@ var VideoCard = React14__namespace.default.memo(({
|
|
|
17289
17461
|
}
|
|
17290
17462
|
);
|
|
17291
17463
|
}, (prevProps, nextProps) => {
|
|
17292
|
-
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;
|
|
17464
|
+
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;
|
|
17293
17465
|
});
|
|
17294
17466
|
VideoCard.displayName = "VideoCard";
|
|
17295
17467
|
var DEFAULT_WORKSPACE_HLS_URLS = {
|
|
@@ -17317,6 +17489,8 @@ var VideoGridView = React14__namespace.default.memo(({
|
|
|
17317
17489
|
const observerRef = React14.useRef(null);
|
|
17318
17490
|
const [gridCols, setGridCols] = React14.useState(4);
|
|
17319
17491
|
const [visibleWorkspaces, setVisibleWorkspaces] = React14.useState(/* @__PURE__ */ new Set());
|
|
17492
|
+
const videoConfig = useVideoConfig();
|
|
17493
|
+
const { cropping, canvasConfig } = videoConfig;
|
|
17320
17494
|
const mergedVideoSources = {
|
|
17321
17495
|
defaultHlsUrl: videoSources.defaultHlsUrl || DEFAULT_HLS_URL,
|
|
17322
17496
|
workspaceHlsUrls: { ...DEFAULT_WORKSPACE_HLS_URLS, ...videoSources.workspaceHlsUrls }
|
|
@@ -17325,6 +17499,13 @@ var VideoGridView = React14__namespace.default.memo(({
|
|
|
17325
17499
|
const wsName = workspaceName.toUpperCase();
|
|
17326
17500
|
return mergedVideoSources.workspaceHlsUrls[wsName] || mergedVideoSources.defaultHlsUrl;
|
|
17327
17501
|
}, [mergedVideoSources]);
|
|
17502
|
+
const getWorkspaceCropping = React14.useCallback((workspaceName) => {
|
|
17503
|
+
if (!cropping) return void 0;
|
|
17504
|
+
if (cropping.workspaceOverrides?.[workspaceName]) {
|
|
17505
|
+
return cropping.workspaceOverrides[workspaceName];
|
|
17506
|
+
}
|
|
17507
|
+
return cropping.default;
|
|
17508
|
+
}, [cropping]);
|
|
17328
17509
|
const veryLowEfficiencyWorkspaces = React14.useMemo(() => {
|
|
17329
17510
|
return new Set(
|
|
17330
17511
|
workspaces.filter((w) => w.efficiency < 50 && w.efficiency >= 10).map((w) => w.workspace_name)
|
|
@@ -17463,6 +17644,7 @@ var VideoGridView = React14__namespace.default.memo(({
|
|
|
17463
17644
|
const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
|
|
17464
17645
|
const isVisible = visibleWorkspaces.has(workspaceId);
|
|
17465
17646
|
const isVeryLowEfficiency = veryLowEfficiencyWorkspaces.has(workspace.workspace_name);
|
|
17647
|
+
const workspaceCropping = getWorkspaceCropping(workspace.workspace_name);
|
|
17466
17648
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
17467
17649
|
"div",
|
|
17468
17650
|
{
|
|
@@ -17477,7 +17659,10 @@ var VideoGridView = React14__namespace.default.memo(({
|
|
|
17477
17659
|
shouldPlay: isVisible,
|
|
17478
17660
|
onClick: () => handleWorkspaceClick(workspace),
|
|
17479
17661
|
onFatalError: throttledReloadDashboard,
|
|
17480
|
-
isVeryLowEfficiency
|
|
17662
|
+
isVeryLowEfficiency,
|
|
17663
|
+
cropping: workspaceCropping,
|
|
17664
|
+
canvasFps: canvasConfig?.fps,
|
|
17665
|
+
useRAF: canvasConfig?.useRAF
|
|
17481
17666
|
}
|
|
17482
17667
|
)
|
|
17483
17668
|
},
|
|
@@ -19663,7 +19848,8 @@ var WorkspaceHistoryCalendar = ({
|
|
|
19663
19848
|
pph: 0,
|
|
19664
19849
|
pphThreshold: 0,
|
|
19665
19850
|
idealOutput: 0,
|
|
19666
|
-
rank: 0
|
|
19851
|
+
rank: 0,
|
|
19852
|
+
idleTime: 0
|
|
19667
19853
|
},
|
|
19668
19854
|
nightShift: {
|
|
19669
19855
|
efficiency: 0,
|
|
@@ -19672,7 +19858,8 @@ var WorkspaceHistoryCalendar = ({
|
|
|
19672
19858
|
pph: 0,
|
|
19673
19859
|
pphThreshold: 0,
|
|
19674
19860
|
idealOutput: 0,
|
|
19675
|
-
rank: 0
|
|
19861
|
+
rank: 0,
|
|
19862
|
+
idleTime: 0
|
|
19676
19863
|
}
|
|
19677
19864
|
});
|
|
19678
19865
|
}
|
|
@@ -19701,7 +19888,8 @@ var WorkspaceHistoryCalendar = ({
|
|
|
19701
19888
|
avgEfficiency: Math.round(validShifts.reduce((sum, shift) => sum + shift.efficiency, 0) / validShifts.length),
|
|
19702
19889
|
avgCycleTime: Math.round(validShifts.reduce((sum, shift) => sum + shift.cycleTime, 0) / validShifts.length),
|
|
19703
19890
|
badDaysCount: badShiftsCount,
|
|
19704
|
-
totalDays: validShifts.length
|
|
19891
|
+
totalDays: validShifts.length,
|
|
19892
|
+
avgIdleTime: Math.round(validShifts.reduce((sum, shift) => sum + (shift.idleTime || 0), 0) / validShifts.length)
|
|
19705
19893
|
};
|
|
19706
19894
|
}, [data, month, year, configuredTimezone]);
|
|
19707
19895
|
const handleDayClick = React14.useCallback((day, shift) => {
|
|
@@ -19872,6 +20060,10 @@ var WorkspaceHistoryCalendar = ({
|
|
|
19872
20060
|
monthlyMetrics.avgCycleTime,
|
|
19873
20061
|
"s"
|
|
19874
20062
|
] })
|
|
20063
|
+
] }),
|
|
20064
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
|
|
20065
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium text-gray-600 mb-1", children: "Avg Idle Time" }),
|
|
20066
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xl font-semibold text-gray-900", children: formatIdleTime(monthlyMetrics.avgIdleTime) })
|
|
19875
20067
|
] })
|
|
19876
20068
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-8 text-gray-500 bg-gray-50 rounded-lg border border-gray-200", children: [
|
|
19877
20069
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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" }) }),
|
|
@@ -22297,12 +22489,12 @@ var getEfficiencyColor = (efficiency) => {
|
|
|
22297
22489
|
return "bg-[#E34329]/90 hover:bg-[#E34329]/95";
|
|
22298
22490
|
}
|
|
22299
22491
|
};
|
|
22300
|
-
var
|
|
22492
|
+
var TREND_STYLES = {
|
|
22301
22493
|
0: { arrow: "\u2193", color: "text-red-500 font-bold text-shadow" },
|
|
22302
22494
|
1: { arrow: "=", color: "text-gray-500 font-bold text-shadow" },
|
|
22303
22495
|
2: { arrow: "\u2191", color: "text-green-500 font-bold text-shadow" }
|
|
22304
22496
|
};
|
|
22305
|
-
var getTrendArrowAndColor2 = (trend) =>
|
|
22497
|
+
var getTrendArrowAndColor2 = (trend) => TREND_STYLES[trend] || { arrow: "", color: "" };
|
|
22306
22498
|
var ARROW_POSITIONS = {
|
|
22307
22499
|
top: "-bottom-6",
|
|
22308
22500
|
"middle-top": "-bottom-6",
|
|
@@ -29989,8 +30181,8 @@ var WorkspaceDetailView = ({
|
|
|
29989
30181
|
if (!dayEntry) {
|
|
29990
30182
|
dayEntry = {
|
|
29991
30183
|
date: dateObj,
|
|
29992
|
-
dayShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0 },
|
|
29993
|
-
nightShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0 }
|
|
30184
|
+
dayShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0, idleTime: 0 },
|
|
30185
|
+
nightShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0, idleTime: 0 }
|
|
29994
30186
|
};
|
|
29995
30187
|
dayDataMap.set(dateKey, dayEntry);
|
|
29996
30188
|
}
|
|
@@ -30002,6 +30194,7 @@ var WorkspaceDetailView = ({
|
|
|
30002
30194
|
shiftTarget.pphThreshold = metric.pph_threshold || 0;
|
|
30003
30195
|
shiftTarget.idealOutput = metric.ideal_output || 0;
|
|
30004
30196
|
shiftTarget.rank = metric.workspace_rank || 0;
|
|
30197
|
+
shiftTarget.idleTime = metric.idle_time || 0;
|
|
30005
30198
|
});
|
|
30006
30199
|
const processedData = Array.from(dayDataMap.values());
|
|
30007
30200
|
console.log(`[handleMonthlyDataLoaded] Transformed data for calendar:`, {
|
|
@@ -30772,6 +30965,7 @@ exports.DEFAULT_ENDPOINTS_CONFIG = DEFAULT_ENDPOINTS_CONFIG;
|
|
|
30772
30965
|
exports.DEFAULT_ENTITY_CONFIG = DEFAULT_ENTITY_CONFIG;
|
|
30773
30966
|
exports.DEFAULT_SHIFT_CONFIG = DEFAULT_SHIFT_CONFIG;
|
|
30774
30967
|
exports.DEFAULT_THEME_CONFIG = DEFAULT_THEME_CONFIG;
|
|
30968
|
+
exports.DEFAULT_VIDEO_CONFIG = DEFAULT_VIDEO_CONFIG;
|
|
30775
30969
|
exports.DEFAULT_WORKSPACE_CONFIG = DEFAULT_WORKSPACE_CONFIG;
|
|
30776
30970
|
exports.DEFAULT_WORKSPACE_POSITIONS = DEFAULT_WORKSPACE_POSITIONS;
|
|
30777
30971
|
exports.DashboardHeader = DashboardHeader;
|
|
@@ -30961,6 +31155,7 @@ exports.useFeatureFlags = useFeatureFlags;
|
|
|
30961
31155
|
exports.useFormatNumber = useFormatNumber;
|
|
30962
31156
|
exports.useHistoricWorkspaceMetrics = useHistoricWorkspaceMetrics;
|
|
30963
31157
|
exports.useHlsStream = useHlsStream;
|
|
31158
|
+
exports.useHlsStreamWithCropping = useHlsStreamWithCropping;
|
|
30964
31159
|
exports.useHookOverride = useHookOverride;
|
|
30965
31160
|
exports.useLeaderboardMetrics = useLeaderboardMetrics;
|
|
30966
31161
|
exports.useLineDetailedMetrics = useLineDetailedMetrics;
|
|
@@ -30982,6 +31177,7 @@ exports.useTargets = useTargets;
|
|
|
30982
31177
|
exports.useTheme = useTheme;
|
|
30983
31178
|
exports.useThemeConfig = useThemeConfig;
|
|
30984
31179
|
exports.useThreads = useThreads;
|
|
31180
|
+
exports.useVideoConfig = useVideoConfig;
|
|
30985
31181
|
exports.useWorkspaceConfig = useWorkspaceConfig;
|
|
30986
31182
|
exports.useWorkspaceDetailedMetrics = useWorkspaceDetailedMetrics;
|
|
30987
31183
|
exports.useWorkspaceDisplayName = useWorkspaceDisplayName;
|