@optifye/dashboard-core 4.2.6 → 4.2.7

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.mjs CHANGED
@@ -2,10 +2,10 @@ import * as React14 from 'react';
2
2
  import React14__default, { createContext, useRef, useCallback, useState, useMemo, useEffect, memo, useContext, useLayoutEffect, useId, Children, isValidElement, useInsertionEffect, forwardRef, Fragment as Fragment$1, createElement, Component } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { useRouter } from 'next/router';
5
- import { subDays, format, parseISO, isValid, isFuture, isToday } from 'date-fns';
6
5
  import { toZonedTime, formatInTimeZone } from 'date-fns-tz';
7
- import { REALTIME_SUBSCRIBE_STATES, createClient } from '@supabase/supabase-js';
6
+ import { subDays, format, parseISO, isValid, isFuture, isToday } from 'date-fns';
8
7
  import mixpanel from 'mixpanel-browser';
8
+ import { REALTIME_SUBSCRIBE_STATES, createClient } from '@supabase/supabase-js';
9
9
  import useSWR from 'swr';
10
10
  import Hls2 from 'hls.js';
11
11
  import { noop, warning, invariant, progress, secondsToMilliseconds, millisecondsToSeconds, memo as memo$1 } from 'motion-utils';
@@ -270,173 +270,6 @@ function useCustomConfig() {
270
270
  const { customConfig } = useDashboardConfig();
271
271
  return customConfig ?? {};
272
272
  }
273
- var AuthContext = createContext({
274
- session: null,
275
- user: null,
276
- loading: true,
277
- error: null,
278
- signOut: async () => {
279
- }
280
- });
281
- var useAuth = () => useContext(AuthContext);
282
- var AuthProvider = ({ children }) => {
283
- const supabase = useSupabase();
284
- const { authConfig } = useDashboardConfig();
285
- const [session, setSession] = useState(null);
286
- const [user, setUser] = useState(null);
287
- const [loading, setLoading] = useState(true);
288
- const [error, setError] = useState(null);
289
- const router = useRouter();
290
- const userProfileTable = authConfig?.userProfileTable;
291
- const roleColumn = authConfig?.roleColumn || "role";
292
- const fetchUserDetails = useCallback(async (supabaseUser) => {
293
- if (!supabaseUser) return null;
294
- const basicUser = {
295
- id: supabaseUser.id,
296
- email: supabaseUser.email
297
- };
298
- if (!userProfileTable || !supabase) return basicUser;
299
- try {
300
- const timeoutPromise = new Promise(
301
- (_, reject) => setTimeout(() => reject(new Error("Profile fetch timeout")), 5e3)
302
- );
303
- const fetchPromise = supabase.from(userProfileTable).select(roleColumn).eq("id", supabaseUser.id).single();
304
- const { data: profile, error: profileError } = await Promise.race([
305
- fetchPromise,
306
- timeoutPromise
307
- ]);
308
- if (profileError) {
309
- if (profileError.message.includes("does not exist") || profileError.message.includes("No rows found") || profileError.code === "PGRST116") {
310
- console.log("User profile table not found or user not in table, using basic auth info");
311
- return basicUser;
312
- }
313
- console.error("Error fetching user profile:", profileError);
314
- return basicUser;
315
- }
316
- const roleValue = profile ? profile[roleColumn] : void 0;
317
- return { ...basicUser, role: roleValue };
318
- } catch (err) {
319
- console.error("Error fetching user profile:", err);
320
- return basicUser;
321
- }
322
- }, [supabase, userProfileTable, roleColumn]);
323
- useEffect(() => {
324
- if (!supabase) return;
325
- let mounted = true;
326
- const safetyTimeout = setTimeout(() => {
327
- if (mounted) {
328
- console.warn("Auth initialization taking too long, forcing loading to false");
329
- setLoading(false);
330
- }
331
- }, 1e4);
332
- const initializeAuth = async () => {
333
- try {
334
- const { data: { session: initialSession }, error: sessionError } = await supabase.auth.getSession();
335
- if (!mounted) return;
336
- if (sessionError) {
337
- setError(sessionError);
338
- setLoading(false);
339
- clearTimeout(safetyTimeout);
340
- return;
341
- }
342
- setSession(initialSession);
343
- if (initialSession?.user) {
344
- try {
345
- const userDetails = await fetchUserDetails(initialSession.user);
346
- if (mounted) setUser(userDetails);
347
- } catch (err) {
348
- console.error("Error fetching user details during init:", err);
349
- if (mounted) {
350
- setUser({
351
- id: initialSession.user.id,
352
- email: initialSession.user.email
353
- });
354
- }
355
- }
356
- }
357
- } catch (err) {
358
- if (mounted) setError(err instanceof Error ? err : new Error("Failed to initialize auth"));
359
- } finally {
360
- if (mounted) {
361
- setLoading(false);
362
- clearTimeout(safetyTimeout);
363
- }
364
- }
365
- };
366
- initializeAuth();
367
- const { data: { subscription } } = supabase.auth.onAuthStateChange(async (_event, currentSession) => {
368
- if (!mounted) return;
369
- setSession(currentSession);
370
- setUser(null);
371
- setLoading(true);
372
- if (currentSession?.user) {
373
- try {
374
- const userDetails = await fetchUserDetails(currentSession.user);
375
- if (mounted) setUser(userDetails);
376
- } catch (err) {
377
- console.error("Error fetching user details on auth state change:", err);
378
- if (mounted) {
379
- setUser({
380
- id: currentSession.user.id,
381
- email: currentSession.user.email
382
- });
383
- }
384
- }
385
- }
386
- if (mounted) setLoading(false);
387
- });
388
- return () => {
389
- mounted = false;
390
- clearTimeout(safetyTimeout);
391
- subscription?.unsubscribe();
392
- };
393
- }, [supabase, fetchUserDetails]);
394
- const signOut = async () => {
395
- if (!supabase) return;
396
- setLoading(true);
397
- const { error: signOutError } = await supabase.auth.signOut();
398
- if (signOutError) setError(signOutError);
399
- const logoutRedirectPath = authConfig?.defaultLogoutRedirect || "/login";
400
- if (router && router.pathname !== logoutRedirectPath && !router.pathname.startsWith(logoutRedirectPath)) {
401
- router.replace(logoutRedirectPath);
402
- }
403
- };
404
- return /* @__PURE__ */ jsx(AuthContext.Provider, { value: { session, user, loading, error, signOut }, children });
405
- };
406
- var defaultContextValue = {
407
- components: {},
408
- hooks: {},
409
- pages: {}
410
- };
411
- var DashboardOverridesContext = createContext(defaultContextValue);
412
- var DashboardOverridesProvider = ({
413
- overrides = defaultContextValue,
414
- children
415
- }) => {
416
- const normalizedOverrides = useMemo(() => {
417
- return {
418
- components: overrides.components || {},
419
- hooks: overrides.hooks || {},
420
- pages: overrides.pages || {}
421
- };
422
- }, [overrides]);
423
- return /* @__PURE__ */ jsx(DashboardOverridesContext.Provider, { value: normalizedOverrides, children });
424
- };
425
- function useComponentOverride(key, Default) {
426
- const { components = {} } = useContext(DashboardOverridesContext);
427
- return components[key] ?? Default;
428
- }
429
- function useHookOverride(key, Default) {
430
- const { hooks = {} } = useContext(DashboardOverridesContext);
431
- return hooks[key] ?? Default;
432
- }
433
- function usePageOverride(key, Default) {
434
- const { pages = {} } = useContext(DashboardOverridesContext);
435
- return pages[key] ?? Default;
436
- }
437
- function useOverrides() {
438
- return useContext(DashboardOverridesContext);
439
- }
440
273
 
441
274
  // src/lib/internal/supabaseClientInstance.ts
442
275
  var supabaseInstance = null;
@@ -451,21 +284,31 @@ var _getSupabaseInstance = () => {
451
284
  }
452
285
  return supabaseInstance;
453
286
  };
454
- var SupabaseContext = createContext(void 0);
455
- var SupabaseProvider = ({ client, children }) => {
456
- _setSupabaseInstance(client);
457
- useEffect(() => {
458
- _setSupabaseInstance(client);
459
- }, [client]);
460
- const contextValue = useMemo(() => ({ supabase: client }), [client]);
461
- return /* @__PURE__ */ jsx(SupabaseContext.Provider, { value: contextValue, children });
287
+
288
+ // src/lib/services/actionService.ts
289
+ var getTable = (dbConfig, tableName) => {
290
+ const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
291
+ const userValue = dbConfig?.tables?.[tableName];
292
+ return userValue ?? defaults2[tableName];
462
293
  };
463
- var useSupabase = () => {
464
- const context = useContext(SupabaseContext);
465
- if (context === void 0) {
466
- throw new Error("useSupabase must be used within a SupabaseProvider.");
294
+ var actionService = {
295
+ async getActionsByName(actionNames, companyIdInput) {
296
+ const supabase = _getSupabaseInstance();
297
+ const config = _getDashboardConfigInstance();
298
+ const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
299
+ const entityConfig = config.entityConfig;
300
+ const actionsTable = getTable(dbConfig, "actions");
301
+ const targetCompanyId = companyIdInput ?? entityConfig?.companyId;
302
+ if (!targetCompanyId) {
303
+ throw new Error("Company ID must be provided either via entityConfig.companyId or as an argument to getActionsByName.");
304
+ }
305
+ const { data, error } = await supabase.from(actionsTable).select("id, action_name, company_id").eq("company_id", targetCompanyId).in("action_name", actionNames);
306
+ if (error) {
307
+ console.error(`Error fetching actions from table ${actionsTable}:`, error);
308
+ throw error;
309
+ }
310
+ return data || [];
467
311
  }
468
- return context.supabase;
469
312
  };
470
313
  var getOperationalDate = (timezone = "Asia/Kolkata", date = /* @__PURE__ */ new Date(), shiftStartTime = "06:00") => {
471
314
  const zonedDate = toZonedTime(date, timezone);
@@ -488,301 +331,57 @@ function getCurrentTimeInZone(timezone, formatString) {
488
331
  return now2;
489
332
  }
490
333
 
491
- // src/lib/utils/database.ts
492
- var getCompanyMetricsTableName = (companyId, prefix = "workspace_performance") => {
493
- if (!companyId) return `${prefix}_unknown_company`;
494
- return `${prefix}_${companyId.replace(/-/g, "_")}`;
334
+ // src/lib/utils/shifts.ts
335
+ var DEFAULT_DAY_SHIFT_START = "06:00";
336
+ var DEFAULT_NIGHT_SHIFT_START = "18:00";
337
+ var DEFAULT_TRANSITION_MINUTES = 15;
338
+ var parseTimeToMinutes = (timeString) => {
339
+ if (!timeString || !/^[0-2]\d:[0-5]\d$/.test(timeString)) {
340
+ return NaN;
341
+ }
342
+ const [hours, minutes] = timeString.split(":").map(Number);
343
+ return hours * 60 + minutes;
495
344
  };
496
- var getMetricsTablePrefix = (companyId) => {
497
- return "performance_metrics";
345
+ var getCurrentShift = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
346
+ const dayShiftStartStr = shiftConfig?.dayShift?.startTime || DEFAULT_DAY_SHIFT_START;
347
+ const nightShiftStartStr = shiftConfig?.nightShift?.startTime || DEFAULT_NIGHT_SHIFT_START;
348
+ const dayShiftId = shiftConfig?.dayShift?.id ?? 0;
349
+ const nightShiftId = shiftConfig?.nightShift?.id ?? 1;
350
+ const dayShiftStartMinutes = parseTimeToMinutes(dayShiftStartStr);
351
+ const nightShiftStartMinutes = parseTimeToMinutes(nightShiftStartStr);
352
+ const effectiveDayStart = !isNaN(dayShiftStartMinutes) ? dayShiftStartMinutes : parseTimeToMinutes(DEFAULT_DAY_SHIFT_START);
353
+ const effectiveNightStart = !isNaN(nightShiftStartMinutes) ? nightShiftStartMinutes : parseTimeToMinutes(DEFAULT_NIGHT_SHIFT_START);
354
+ const zonedNow = toZonedTime(now2, timezone);
355
+ const currentHour = zonedNow.getHours();
356
+ const currentMinutes = zonedNow.getMinutes();
357
+ const currentTotalMinutes = currentHour * 60 + currentMinutes;
358
+ const operationalDate = getOperationalDate(timezone, zonedNow, dayShiftStartStr);
359
+ let determinedShiftId;
360
+ if (effectiveDayStart < effectiveNightStart) {
361
+ if (currentTotalMinutes >= effectiveDayStart && currentTotalMinutes < effectiveNightStart) {
362
+ determinedShiftId = dayShiftId;
363
+ } else {
364
+ determinedShiftId = nightShiftId;
365
+ }
366
+ } else {
367
+ if (currentTotalMinutes >= effectiveDayStart || currentTotalMinutes < effectiveNightStart) {
368
+ determinedShiftId = dayShiftId;
369
+ } else {
370
+ determinedShiftId = nightShiftId;
371
+ }
372
+ }
373
+ return { shiftId: determinedShiftId, date: operationalDate };
498
374
  };
499
-
500
- // src/lib/hooks/useMetrics.ts
501
- var DEFAULT_COMPANY_ID = "default-company-id";
502
- var useWorkspaceMetrics = (workspaceId) => {
503
- const supabase = useSupabase();
504
- const entityConfig = useEntityConfig();
505
- useDatabaseConfig();
506
- const dateTimeConfig = useDateTimeConfig();
507
- const [workspaceMetrics, setWorkspaceMetrics] = useState(null);
508
- const [isLoading, setIsLoading] = useState(true);
509
- const [error, setError] = useState(null);
510
- const fetchWorkspaceMetrics = useCallback(async () => {
511
- try {
512
- const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
513
- const { data, error: fetchError } = await supabase.from("overview_workspace_metrics").select("*").eq("workspace_id", workspaceId).eq("date", operationalDate).single();
514
- if (fetchError) throw fetchError;
515
- setWorkspaceMetrics(data);
516
- } catch (err) {
517
- setError({ message: err.message, code: err.code });
518
- console.error("Error fetching workspace metrics:", err);
519
- } finally {
520
- setIsLoading(false);
521
- }
522
- }, [supabase, workspaceId, dateTimeConfig.defaultTimezone]);
523
- useEffect(() => {
524
- let channels = [];
525
- const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
526
- const setupSubscriptions = () => {
527
- const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
528
- const metricsTablePrefix = getMetricsTablePrefix();
529
- const metricsTable = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
530
- const metricsChannel = supabase.channel("workspace-metrics").on(
531
- "postgres_changes",
532
- {
533
- event: "*",
534
- schema: "public",
535
- table: metricsTable,
536
- filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
537
- },
538
- async (payload) => {
539
- console.log(`Received ${metricsTablePrefix} update:`, payload);
540
- await fetchWorkspaceMetrics();
541
- }
542
- ).subscribe((status) => {
543
- console.log(`${metricsTablePrefix} subscription status:`, status);
544
- });
545
- const overviewChannel = supabase.channel("workspace-overview-metrics").on(
546
- "postgres_changes",
547
- {
548
- event: "*",
549
- schema: "public",
550
- table: "overview_workspace_metrics",
551
- filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
552
- },
553
- async (payload) => {
554
- console.log("Received overview metrics update:", payload);
555
- await fetchWorkspaceMetrics();
556
- }
557
- ).subscribe((status) => {
558
- console.log("Overview metrics subscription status:", status);
559
- });
560
- channels = [metricsChannel, overviewChannel];
561
- };
562
- fetchWorkspaceMetrics();
563
- setupSubscriptions();
564
- return () => {
565
- channels.forEach((channel) => {
566
- console.log("Cleaning up channel subscription");
567
- supabase.removeChannel(channel);
568
- });
569
- };
570
- }, [supabase, workspaceId, fetchWorkspaceMetrics, entityConfig.companyId, dateTimeConfig.defaultTimezone]);
571
- return { workspaceMetrics, isLoading, error, refetch: fetchWorkspaceMetrics };
572
- };
573
- var useLineMetrics = (lineId) => {
574
- const supabase = useSupabase();
575
- const dateTimeConfig = useDateTimeConfig();
576
- const [lineMetrics, setLineMetrics] = useState(null);
577
- const [isLoading, setIsLoading] = useState(true);
578
- const [error, setError] = useState(null);
579
- const fetchLineMetrics = useCallback(async () => {
580
- try {
581
- const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
582
- const { data, error: fetchError } = await supabase.from("overview_line_metrics").select("*").eq("line_id", lineId).eq("date", operationalDate).single();
583
- if (fetchError) throw fetchError;
584
- setLineMetrics(data);
585
- } catch (err) {
586
- setError({ message: err.message, code: err.code });
587
- console.error("Error fetching line metrics:", err);
588
- } finally {
589
- setIsLoading(false);
590
- }
591
- }, [supabase, lineId, dateTimeConfig.defaultTimezone]);
592
- useEffect(() => {
593
- let channels = [];
594
- const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
595
- const setupSubscriptions = () => {
596
- const lineMetricsChannel = supabase.channel("line-base-metrics").on(
597
- "postgres_changes",
598
- {
599
- event: "*",
600
- schema: "public",
601
- table: "line_metrics",
602
- filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
603
- },
604
- async (payload) => {
605
- console.log("Received line metrics update:", payload);
606
- await fetchLineMetrics();
607
- }
608
- ).subscribe((status) => {
609
- console.log("Line metrics subscription status:", status);
610
- });
611
- const overviewChannel = supabase.channel("line-overview-metrics").on(
612
- "postgres_changes",
613
- {
614
- event: "*",
615
- schema: "public",
616
- table: "overview_line_metrics",
617
- filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
618
- },
619
- async (payload) => {
620
- console.log("Received line overview update:", payload);
621
- await fetchLineMetrics();
622
- }
623
- ).subscribe((status) => {
624
- console.log("Line overview subscription status:", status);
625
- });
626
- channels = [lineMetricsChannel, overviewChannel];
627
- };
628
- fetchLineMetrics();
629
- setupSubscriptions();
630
- return () => {
631
- channels.forEach((channel) => {
632
- console.log("Cleaning up channel subscription");
633
- supabase.removeChannel(channel);
634
- });
635
- };
636
- }, [supabase, lineId, fetchLineMetrics, dateTimeConfig.defaultTimezone]);
637
- return { lineMetrics, isLoading, error, refetch: fetchLineMetrics };
638
- };
639
- var useMetrics = (tableName, options) => {
640
- const supabase = useSupabase();
641
- const entityConfig = useEntityConfig();
642
- const [data, setData] = useState([]);
643
- const [isLoading, setIsLoading] = useState(true);
644
- const [error, setError] = useState(null);
645
- const channelRef = useRef(null);
646
- useEffect(() => {
647
- const fetchData = async () => {
648
- try {
649
- setIsLoading(true);
650
- setError(null);
651
- let actualTableName = tableName;
652
- if (tableName === "metrics") {
653
- const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
654
- const metricsTablePrefix = getMetricsTablePrefix(companyId);
655
- actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
656
- }
657
- let query = supabase.from(actualTableName).select("*");
658
- if (options?.filter) {
659
- Object.entries(options.filter).forEach(([key, value]) => {
660
- query = query.eq(key, value);
661
- });
662
- }
663
- const { data: result, error: fetchError } = await query;
664
- if (fetchError) throw fetchError;
665
- setData(result);
666
- } catch (err) {
667
- console.error(`Error fetching data from ${tableName}:`, err);
668
- setError({ message: err.message, code: err.code || "FETCH_ERROR" });
669
- } finally {
670
- setIsLoading(false);
671
- }
672
- };
673
- const setupSubscription = () => {
674
- if (!options?.realtime) return;
675
- let actualTableName = tableName;
676
- if (tableName === "metrics") {
677
- const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
678
- const metricsTablePrefix = getMetricsTablePrefix();
679
- actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
680
- }
681
- const filter2 = {};
682
- if (options?.filter) {
683
- Object.entries(options.filter).forEach(([key, value]) => {
684
- filter2[`${key}=eq.${value}`] = value;
685
- });
686
- }
687
- channelRef.current = supabase.channel(`${tableName}-changes`).on(
688
- "postgres_changes",
689
- {
690
- event: "*",
691
- schema: "public",
692
- table: actualTableName,
693
- filter: Object.keys(filter2).length > 0 ? Object.keys(filter2).join(",") : void 0
694
- },
695
- () => {
696
- fetchData();
697
- }
698
- ).subscribe();
699
- };
700
- fetchData();
701
- setupSubscription();
702
- return () => {
703
- if (channelRef.current) {
704
- supabase.removeChannel(channelRef.current);
705
- }
706
- };
707
- }, [supabase, tableName, options, entityConfig.companyId]);
708
- const refetch = async () => {
709
- setIsLoading(true);
710
- try {
711
- let actualTableName = tableName;
712
- if (tableName === "metrics") {
713
- const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
714
- const metricsTablePrefix = getMetricsTablePrefix(companyId);
715
- actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
716
- }
717
- let query = supabase.from(actualTableName).select("*");
718
- if (options?.filter) {
719
- Object.entries(options.filter).forEach(([key, value]) => {
720
- query = query.eq(key, value);
721
- });
722
- }
723
- const { data: result, error: fetchError } = await query;
724
- if (fetchError) throw fetchError;
725
- setData(result);
726
- setError(null);
727
- } catch (err) {
728
- console.error(`Error refetching data from ${tableName}:`, err);
729
- setError({ message: err.message, code: err.code || "FETCH_ERROR" });
730
- } finally {
731
- setIsLoading(false);
732
- }
733
- };
734
- return { data, isLoading, error, refetch };
735
- };
736
- var DEFAULT_DAY_SHIFT_START = "06:00";
737
- var DEFAULT_NIGHT_SHIFT_START = "18:00";
738
- var DEFAULT_TRANSITION_MINUTES = 15;
739
- var parseTimeToMinutes = (timeString) => {
740
- if (!timeString || !/^[0-2]\d:[0-5]\d$/.test(timeString)) {
741
- return NaN;
742
- }
743
- const [hours, minutes] = timeString.split(":").map(Number);
744
- return hours * 60 + minutes;
745
- };
746
- var getCurrentShift = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
375
+ var isTransitionPeriod = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
747
376
  const dayShiftStartStr = shiftConfig?.dayShift?.startTime || DEFAULT_DAY_SHIFT_START;
748
377
  const nightShiftStartStr = shiftConfig?.nightShift?.startTime || DEFAULT_NIGHT_SHIFT_START;
749
- const dayShiftId = shiftConfig?.dayShift?.id ?? 0;
750
- const nightShiftId = shiftConfig?.nightShift?.id ?? 1;
378
+ const transitionMinutes = shiftConfig?.transitionPeriodMinutes ?? DEFAULT_TRANSITION_MINUTES;
751
379
  const dayShiftStartMinutes = parseTimeToMinutes(dayShiftStartStr);
752
380
  const nightShiftStartMinutes = parseTimeToMinutes(nightShiftStartStr);
753
- const effectiveDayStart = !isNaN(dayShiftStartMinutes) ? dayShiftStartMinutes : parseTimeToMinutes(DEFAULT_DAY_SHIFT_START);
754
- const effectiveNightStart = !isNaN(nightShiftStartMinutes) ? nightShiftStartMinutes : parseTimeToMinutes(DEFAULT_NIGHT_SHIFT_START);
755
- const zonedNow = toZonedTime(now2, timezone);
756
- const currentHour = zonedNow.getHours();
757
- const currentMinutes = zonedNow.getMinutes();
758
- const currentTotalMinutes = currentHour * 60 + currentMinutes;
759
- const operationalDate = getOperationalDate(timezone, zonedNow, dayShiftStartStr);
760
- let determinedShiftId;
761
- if (effectiveDayStart < effectiveNightStart) {
762
- if (currentTotalMinutes >= effectiveDayStart && currentTotalMinutes < effectiveNightStart) {
763
- determinedShiftId = dayShiftId;
764
- } else {
765
- determinedShiftId = nightShiftId;
766
- }
767
- } else {
768
- if (currentTotalMinutes >= effectiveDayStart || currentTotalMinutes < effectiveNightStart) {
769
- determinedShiftId = dayShiftId;
770
- } else {
771
- determinedShiftId = nightShiftId;
772
- }
773
- }
774
- return { shiftId: determinedShiftId, date: operationalDate };
775
- };
776
- var isTransitionPeriod = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date()) => {
777
- const dayShiftStartStr = shiftConfig?.dayShift?.startTime || DEFAULT_DAY_SHIFT_START;
778
- const nightShiftStartStr = shiftConfig?.nightShift?.startTime || DEFAULT_NIGHT_SHIFT_START;
779
- const transitionMinutes = shiftConfig?.transitionPeriodMinutes ?? DEFAULT_TRANSITION_MINUTES;
780
- const dayShiftStartMinutes = parseTimeToMinutes(dayShiftStartStr);
781
- const nightShiftStartMinutes = parseTimeToMinutes(nightShiftStartStr);
782
- if (isNaN(dayShiftStartMinutes) || isNaN(nightShiftStartMinutes)) {
783
- return false;
784
- }
785
- const transitionTimes = [dayShiftStartMinutes, nightShiftStartMinutes];
381
+ if (isNaN(dayShiftStartMinutes) || isNaN(nightShiftStartMinutes)) {
382
+ return false;
383
+ }
384
+ const transitionTimes = [dayShiftStartMinutes, nightShiftStartMinutes];
786
385
  const zonedNow = toZonedTime(now2, timezone);
787
386
  const currentHour = zonedNow.getHours();
788
387
  const currentMinutes = zonedNow.getMinutes();
@@ -808,748 +407,246 @@ var isTransitionPeriod = (timezone, shiftConfig, now2 = /* @__PURE__ */ new Date
808
407
  });
809
408
  };
810
409
 
811
- // src/lib/hooks/useWorkspaceDetailedMetrics.ts
812
- var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId) => {
813
- const entityConfig = useEntityConfig();
814
- const databaseConfig = useDatabaseConfig();
815
- const dateTimeConfig = useDateTimeConfig();
816
- const shiftConfig = useShiftConfig();
817
- const workspaceConfig = useWorkspaceConfig();
818
- const supabase = useSupabase();
819
- const [metrics2, setMetrics] = useState(null);
820
- const [isLoading, setIsLoading] = useState(true);
821
- const [error, setError] = useState(null);
822
- const updateQueueRef = useRef(false);
823
- const isFetchingRef = useRef(false);
824
- const timeoutRef = useRef(null);
825
- const channelRef = useRef(null);
826
- const schema = databaseConfig.schema ?? "public";
827
- const companyId = entityConfig.companyId || "";
828
- const metricsTablePrefix = getMetricsTablePrefix();
829
- const metricsTable = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
830
- const defaultTimezone = dateTimeConfig.defaultTimezone;
831
- const workspaceMetricsBaseTable = databaseConfig.tables?.workspaces ?? "workspace_metrics";
832
- const workspaceActionsTable = databaseConfig.tables?.actions ?? "workspace_actions";
833
- const fetchMetrics = useCallback(async () => {
834
- if (!workspaceId || isFetchingRef.current) return;
835
- try {
836
- isFetchingRef.current = true;
837
- const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
838
- const queryDate = date || currentShift.date;
839
- const queryShiftId = shiftId !== void 0 ? shiftId : currentShift.shiftId;
840
- console.log("[useWorkspaceDetailedMetrics] Using shift ID:", queryShiftId, "from input shift:", shiftId);
841
- console.log("[useWorkspaceDetailedMetrics] Using date:", queryDate, "from input date:", date);
842
- console.log(`[useWorkspaceDetailedMetrics] Querying ${metricsTable} for workspace: ${workspaceId}, date: ${queryDate}, shift: ${queryShiftId}`);
843
- const { data, error: fetchError } = await supabase.from(metricsTable).select("*").eq("workspace_id", workspaceId).eq("date", queryDate).eq("shift_id", queryShiftId).maybeSingle();
844
- if (fetchError) throw fetchError;
845
- if (!data && !date && shiftId === void 0) {
846
- console.log("[useWorkspaceDetailedMetrics] No data found for current date/shift, attempting to find most recent data...");
847
- 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();
848
- if (recentError) throw recentError;
849
- if (recentData) {
850
- console.log(`[useWorkspaceDetailedMetrics] Found fallback data from date: ${recentData.date}, shift: ${recentData.shift_id}`);
851
- const outputDifference2 = (recentData.total_output || 0) - (recentData.ideal_output || 0);
852
- const workspaceMatch2 = recentData.workspace_name?.match(/WS(\d+)/);
853
- const workspaceNumber2 = workspaceMatch2 ? parseInt(workspaceMatch2[1]) : 0;
854
- const specialWsStart2 = workspaceConfig.specialWorkspaces?.startId ?? 19;
855
- const specialWsEnd2 = workspaceConfig.specialWorkspaces?.endId ?? 34;
856
- const isSpecialWorkspace2 = workspaceNumber2 >= specialWsStart2 && workspaceNumber2 <= specialWsEnd2;
857
- const outputHourly2 = recentData.output_hourly || {};
858
- const hasOutputHourlyData2 = outputHourly2 && typeof outputHourly2 === "object" && Object.keys(outputHourly2).length > 0;
859
- let hourlyActionCounts2 = [];
860
- if (hasOutputHourlyData2) {
861
- console.log("Using new output_hourly column for workspace (fallback):", recentData.workspace_name);
862
- console.log("Raw output_hourly data (fallback):", outputHourly2);
863
- const isNightShift = recentData.shift_id === 1;
864
- const shiftStart = recentData.shift_start || (isNightShift ? "22:00" : "06:00");
865
- const shiftEnd = recentData.shift_end || (isNightShift ? "06:00" : "14:00");
866
- const startHour = parseInt(shiftStart.split(":")[0]);
867
- let expectedHours = [];
868
- if (isNightShift) {
869
- for (let i = 0; i < 9; i++) {
870
- expectedHours.push((startHour + i) % 24);
871
- }
872
- } else {
873
- for (let i = 0; i < 9; i++) {
874
- expectedHours.push((startHour + i) % 24);
875
- }
876
- }
877
- console.log("Expected shift hours (fallback):", expectedHours);
878
- console.log("Available data hours (fallback):", Object.keys(outputHourly2));
879
- hourlyActionCounts2 = expectedHours.map((expectedHour) => {
880
- let hourData = outputHourly2[expectedHour.toString()];
881
- if (!hourData && isNightShift) {
882
- for (const [storedHour, data2] of Object.entries(outputHourly2)) {
883
- if (Array.isArray(data2) && data2.length > 0 && data2.some((val) => val > 0)) {
884
- if (storedHour === "18" && expectedHour === 1) {
885
- hourData = data2;
886
- console.log(`Mapping stored hour ${storedHour} to expected hour ${expectedHour} (fallback)`);
887
- break;
888
- }
889
- }
890
- }
891
- }
892
- return Array.isArray(hourData) ? hourData.reduce((sum, count) => sum + (count || 0), 0) : 0;
893
- });
894
- console.log("Final hourly action counts (fallback):", hourlyActionCounts2);
895
- } else {
896
- console.log("Using output_array fallback for workspace (fallback):", recentData.workspace_name);
897
- const minuteByMinuteArray = recentData.output_array || [];
898
- if (isSpecialWorkspace2) {
899
- const last40Readings = minuteByMinuteArray.slice(Math.max(0, minuteByMinuteArray.length - 40));
900
- hourlyActionCounts2 = last40Readings;
901
- } else {
902
- for (let i = 0; i < minuteByMinuteArray.length; i += 60) {
903
- const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
904
- const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
905
- hourlyActionCounts2.push(hourlySum);
906
- }
907
- }
908
- }
909
- const transformedData2 = {
910
- workspace_id: recentData.workspace_id,
911
- workspace_name: recentData.workspace_name,
912
- line_id: recentData.line_id,
913
- line_name: recentData.line_name || "Line 1",
914
- company_id: recentData.company_id || companyId,
915
- company_name: recentData.company_name || "Nahar Group",
916
- date: recentData.date,
917
- shift_id: recentData.shift_id,
918
- action_name: recentData.action_name || "",
919
- shift_start: recentData.shift_start || "06:00",
920
- shift_end: recentData.shift_end || "14:00",
921
- shift_type: recentData.shift_type || (recentData.shift_id === 0 ? "Day" : "Night"),
922
- pph_threshold: recentData.pph_threshold || 0,
923
- target_output: recentData.total_day_output || 0,
924
- avg_pph: recentData.avg_pph || 0,
925
- avg_cycle_time: recentData.avg_cycle_time || 0,
926
- ideal_cycle_time: recentData.ideal_cycle_time || 0,
927
- avg_efficiency: recentData.efficiency || 0,
928
- total_actions: recentData.total_output || 0,
929
- hourly_action_counts: hourlyActionCounts2,
930
- // Now uses the NEW logic with fallback
931
- workspace_rank: recentData.workspace_rank || 0,
932
- total_workspaces: recentData.total_workspaces || workspaceConfig.totalWorkspaces || 42,
933
- ideal_output_until_now: recentData.ideal_output || 0,
934
- output_difference: outputDifference2,
935
- idle_time: recentData.idle_time || 0,
936
- idle_time_hourly: recentData.idle_time_hourly || void 0,
937
- ...recentData.compliance_efficiency !== void 0 && { compliance_efficiency: recentData.compliance_efficiency },
938
- ...recentData.sop_check !== void 0 && { sop_check: recentData.sop_check }
939
- };
940
- setMetrics(transformedData2);
941
- setIsLoading(false);
942
- updateQueueRef.current = false;
943
- isFetchingRef.current = false;
944
- return;
945
- } else {
946
- console.warn("[useWorkspaceDetailedMetrics] No data found for workspace:", workspaceId, "at all");
947
- }
948
- }
949
- if (!data) {
950
- console.warn("[useWorkspaceDetailedMetrics] No detailed metrics found for workspace:", workspaceId);
951
- setMetrics(null);
952
- setIsLoading(false);
953
- updateQueueRef.current = false;
954
- isFetchingRef.current = false;
955
- return;
410
+ // src/lib/utils/database.ts
411
+ var getCompanyMetricsTableName = (companyId, prefix = "workspace_performance") => {
412
+ if (!companyId) return `${prefix}_unknown_company`;
413
+ return `${prefix}_${companyId.replace(/-/g, "_")}`;
414
+ };
415
+ var getMetricsTablePrefix = (companyId) => {
416
+ return "performance_metrics";
417
+ };
418
+
419
+ // src/lib/services/dashboardService.ts
420
+ var getTable2 = (dbConfig, tableName) => {
421
+ const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
422
+ const userValue = dbConfig?.tables?.[tableName];
423
+ return userValue ?? defaults2[tableName];
424
+ };
425
+ var dashboardService = {
426
+ // Example for getLineInfo:
427
+ async getLineInfo(lineIdInput) {
428
+ const supabase = _getSupabaseInstance();
429
+ const config = _getDashboardConfigInstance();
430
+ const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
431
+ const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
432
+ const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
433
+ const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
434
+ const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
435
+ const linesTable = getTable2(dbConfig, "lines");
436
+ const lineMetricsTable = getTable2(dbConfig, "lineMetrics");
437
+ const companyId = entityConfig.companyId;
438
+ const metricsTablePrefixStr = getMetricsTablePrefix();
439
+ const metricsTable = `${metricsTablePrefixStr}_${companyId ? companyId.replace(/-/g, "_") : "unknown_company"}`;
440
+ const defaultLineId = entityConfig.defaultLineId;
441
+ const secondaryLineId = entityConfig.secondaryLineId;
442
+ const factoryViewId = entityConfig.factoryViewId ?? "factory";
443
+ const defaultTimezone = dateTimeConfig.defaultTimezone;
444
+ const { shiftId, date } = getCurrentShift(defaultTimezone, shiftConfig);
445
+ const lineId = lineIdInput;
446
+ if (lineId === factoryViewId) {
447
+ if (!defaultLineId || !secondaryLineId || !companyId) {
448
+ throw new Error("Factory View requires defaultLineId, secondaryLineId, and companyId to be configured.");
956
449
  }
957
- const outputDifference = (data.total_output || 0) - (data.ideal_output || 0);
958
- const workspaceMatch = data.workspace_name?.match(/WS(\d+)/);
959
- const workspaceNumber = workspaceMatch ? parseInt(workspaceMatch[1]) : 0;
960
- const specialWsStart = workspaceConfig.specialWorkspaces?.startId ?? 19;
961
- const specialWsEnd = workspaceConfig.specialWorkspaces?.endId ?? 34;
962
- const isSpecialWorkspace = workspaceNumber >= specialWsStart && workspaceNumber <= specialWsEnd;
963
- const outputHourly = data.output_hourly || {};
964
- console.log("[DEBUG] Raw data.output_hourly:", data.output_hourly);
965
- console.log("[DEBUG] outputHourly after || {}:", outputHourly);
966
- console.log("[DEBUG] typeof outputHourly:", typeof outputHourly);
967
- console.log("[DEBUG] Object.keys(outputHourly):", Object.keys(outputHourly));
968
- console.log("[DEBUG] Object.keys(outputHourly).length:", Object.keys(outputHourly).length);
969
- const hasOutputHourlyData = outputHourly && typeof outputHourly === "object" && Object.keys(outputHourly).length > 0;
970
- console.log("[DEBUG] hasOutputHourlyData:", hasOutputHourlyData);
971
- let hourlyActionCounts = [];
972
- if (hasOutputHourlyData) {
973
- console.log("\u2705 Using new output_hourly column for workspace:", data.workspace_name);
974
- console.log("Raw output_hourly data:", JSON.stringify(outputHourly));
975
- const isNightShift = data.shift_id === 1;
976
- const shiftStart = data.shift_start || (isNightShift ? "22:00" : "06:00");
977
- const shiftEnd = data.shift_end || (isNightShift ? "06:00" : "14:00");
978
- const startHour = parseInt(shiftStart.split(":")[0]);
979
- let expectedHours = [];
980
- if (isNightShift) {
981
- for (let i = 0; i < 9; i++) {
982
- expectedHours.push((startHour + i) % 24);
983
- }
984
- } else {
985
- for (let i = 0; i < 9; i++) {
986
- expectedHours.push((startHour + i) % 24);
987
- }
988
- }
989
- console.log("Expected shift hours:", expectedHours);
990
- console.log("Available data hours:", Object.keys(outputHourly));
991
- hourlyActionCounts = expectedHours.map((expectedHour) => {
992
- let hourData = outputHourly[expectedHour.toString()];
993
- if (!hourData && isNightShift) {
994
- for (const [storedHour, data2] of Object.entries(outputHourly)) {
995
- if (Array.isArray(data2) && data2.length > 0 && data2.some((val) => val > 0)) {
996
- if (storedHour === "18" && expectedHour === 1) {
997
- hourData = data2;
998
- console.log(`Mapping stored hour ${storedHour} to expected hour ${expectedHour}`);
999
- break;
1000
- }
1001
- }
1002
- }
1003
- }
1004
- return Array.isArray(hourData) ? hourData.reduce((sum, count) => sum + (count || 0), 0) : 0;
1005
- });
1006
- console.log("Final hourly action counts:", hourlyActionCounts);
1007
- } else {
1008
- console.log("\u274C Using output_array fallback for workspace:", data.workspace_name);
1009
- console.log("[DEBUG] Fallback reason - hasOutputHourlyData is false");
1010
- console.log("[DEBUG] data.output_hourly was:", data.output_hourly);
1011
- const minuteByMinuteArray = data.output_array || [];
1012
- if (isSpecialWorkspace) {
1013
- const last40Readings = minuteByMinuteArray.slice(Math.max(0, minuteByMinuteArray.length - 40));
1014
- hourlyActionCounts = last40Readings;
1015
- } else {
1016
- for (let i = 0; i < minuteByMinuteArray.length; i += 60) {
1017
- const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
1018
- const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
1019
- hourlyActionCounts.push(hourlySum);
1020
- }
450
+ const { data: lineData2, error: lineError2 } = await supabase.from(linesTable).select(`
451
+ id,
452
+ line_name,
453
+ factory_id,
454
+ factories!lines_factory_id_fkey(factory_name),
455
+ company_id,
456
+ companies!lines_company_id_fkey(company_name:name)
457
+ `).eq("id", defaultLineId).maybeSingle();
458
+ if (lineError2) throw lineError2;
459
+ if (!lineData2) throw new Error(`Configured default line (${defaultLineId}) not found`);
460
+ const { data: metricsData, error: metricsError } = await supabase.from(lineMetricsTable).select("*").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
461
+ if (metricsError) throw metricsError;
462
+ const { data: performanceData2, error: performanceError2 } = await supabase.from(metricsTable).select("efficiency").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
463
+ if (performanceError2) throw performanceError2;
464
+ const underperformingCount2 = performanceData2?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
465
+ const totalValidWorkspaces2 = performanceData2?.filter((w) => w.efficiency >= 10).length || 0;
466
+ const combinedMetrics = (metricsData || []).reduce((acc, m) => {
467
+ acc.avg_efficiency += m.efficiency ?? 0;
468
+ acc.current_output += m.current_output ?? 0;
469
+ acc.ideal_output += m.ideal_output ?? m.line_threshold ?? 0;
470
+ return acc;
471
+ }, { avg_efficiency: 0, current_output: 0, ideal_output: 0 });
472
+ metricsData?.length || 1;
473
+ return {
474
+ line_id: factoryViewId,
475
+ // Use configured factory view ID
476
+ line_name: "Factory View",
477
+ // Consider making this configurable?
478
+ company_id: lineData2.company_id,
479
+ company_name: lineData2.companies?.[0]?.company_name ?? "",
480
+ factory_id: lineData2.factory_id,
481
+ factory_name: lineData2.factories?.[0]?.factory_name ?? "",
482
+ shift_id: shiftId,
483
+ date,
484
+ metrics: {
485
+ avg_efficiency: (performanceData2?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces2 || 1),
486
+ // Use performance data for avg efficiency
487
+ avg_cycle_time: 0,
488
+ // Needs calculation logic if required for factory view
489
+ current_output: combinedMetrics.current_output,
490
+ ideal_output: combinedMetrics.ideal_output,
491
+ total_workspaces: 44,
492
+ // SRC ALIGNMENT: Use hardcoded 44 for factory view total_workspaces
493
+ underperforming_workspaces: underperformingCount2,
494
+ underperforming_workspace_names: [],
495
+ // Populate if needed
496
+ underperforming_workspace_uuids: [],
497
+ // Populate if needed
498
+ output_array: [],
499
+ // Combine if needed
500
+ line_threshold: combinedMetrics.ideal_output,
501
+ threshold_pph: 0,
502
+ // Needs calculation logic if required
503
+ shift_start: shiftConfig.dayShift?.startTime || "06:00",
504
+ // Use config
505
+ shift_end: shiftConfig.dayShift?.endTime || "18:00",
506
+ // Use config
507
+ last_updated: (/* @__PURE__ */ new Date()).toISOString(),
508
+ poorest_performing_workspaces: []
509
+ // Populate if needed
1021
510
  }
1022
- console.log("Final hourly action counts:", hourlyActionCounts);
1023
- }
1024
- const transformedData = {
1025
- workspace_id: data.workspace_id,
1026
- workspace_name: data.workspace_name,
1027
- line_id: data.line_id,
1028
- line_name: data.line_name || "Line 1",
1029
- company_id: data.company_id || companyId,
1030
- company_name: data.company_name || "Nahar Group",
1031
- date: data.date,
1032
- shift_id: data.shift_id,
1033
- action_name: data.action_name || "",
1034
- shift_start: data.shift_start || "06:00",
1035
- shift_end: data.shift_end || "14:00",
1036
- shift_type: data.shift_type || (data.shift_id === 0 ? "Day" : "Night"),
1037
- pph_threshold: data.pph_threshold || 0,
1038
- target_output: data.total_day_output || 0,
1039
- avg_pph: data.avg_pph || 0,
1040
- avg_cycle_time: data.avg_cycle_time || 0,
1041
- ideal_cycle_time: data.ideal_cycle_time || 0,
1042
- avg_efficiency: data.efficiency || 0,
1043
- total_actions: data.total_output || 0,
1044
- hourly_action_counts: hourlyActionCounts,
1045
- workspace_rank: data.workspace_rank || 0,
1046
- total_workspaces: data.total_workspaces || workspaceConfig.totalWorkspaces || 42,
1047
- ideal_output_until_now: data.ideal_output || 0,
1048
- output_difference: outputDifference,
1049
- idle_time: data.idle_time || 0,
1050
- // Add idle_time from performance_metrics table
1051
- idle_time_hourly: data.idle_time_hourly || void 0,
1052
- // Add idle_time_hourly from performance_metrics table
1053
- ...data.compliance_efficiency !== void 0 && { compliance_efficiency: data.compliance_efficiency },
1054
- ...data.sop_check !== void 0 && { sop_check: data.sop_check }
1055
511
  };
1056
- setMetrics(transformedData);
512
+ }
513
+ if (!companyId) {
514
+ throw new Error("Company ID must be configured for individual line requests.");
515
+ }
516
+ 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();
517
+ if (lineError) throw lineError;
518
+ if (!lineData) throw new Error(`Line with ID ${lineId} not found`);
519
+ let metricsFromDb = null;
520
+ try {
521
+ const { data: fetchedMetrics, error } = await supabase.from(lineMetricsTable).select("*").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date).maybeSingle();
522
+ if (error) throw error;
523
+ metricsFromDb = fetchedMetrics;
1057
524
  } catch (err) {
1058
- console.error("Error fetching workspace metrics:", err);
1059
- setError({ message: err.message, code: err.code });
1060
- } finally {
1061
- isFetchingRef.current = false;
1062
- updateQueueRef.current = false;
1063
- setIsLoading(false);
525
+ console.error(`Error fetching line metrics for ${lineId}:`, err);
1064
526
  }
1065
- }, [supabase, workspaceId, date, shiftId, metricsTable, defaultTimezone, shiftConfig, workspaceConfig, companyId]);
1066
- const queueUpdate = useCallback(() => {
1067
- if (!workspaceId || updateQueueRef.current) return;
1068
- updateQueueRef.current = true;
1069
- if (timeoutRef.current) {
1070
- clearTimeout(timeoutRef.current);
527
+ const { data: performanceData, error: performanceError } = await supabase.from(metricsTable).select("efficiency").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date);
528
+ if (performanceError) throw performanceError;
529
+ const underperformingCount = performanceData?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
530
+ const totalValidWorkspaces = performanceData?.filter((w) => w.efficiency >= 10).length || 0;
531
+ const avgEfficiencyFromPerf = (performanceData?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces || 1);
532
+ const useFallbackMetrics = !metricsFromDb;
533
+ let metricsForReturn;
534
+ if (useFallbackMetrics) {
535
+ metricsForReturn = {
536
+ avg_efficiency: 0,
537
+ avg_cycle_time: 0,
538
+ current_output: 0,
539
+ ideal_output: 0,
540
+ total_workspaces: 42,
541
+ underperforming_workspaces: underperformingCount,
542
+ underperforming_workspace_names: [],
543
+ underperforming_workspace_uuids: [],
544
+ output_array: [],
545
+ line_threshold: 0,
546
+ threshold_pph: 0,
547
+ shift_start: "06:00",
548
+ shift_end: "14:00",
549
+ last_updated: (/* @__PURE__ */ new Date()).toISOString(),
550
+ poorest_performing_workspaces: []
551
+ };
552
+ } else {
553
+ metricsForReturn = {
554
+ avg_efficiency: metricsFromDb.efficiency ?? avgEfficiencyFromPerf,
555
+ avg_cycle_time: metricsFromDb.avg_cycle_time || 0,
556
+ current_output: metricsFromDb.current_output || 0,
557
+ ideal_output: metricsFromDb.ideal_output || metricsFromDb.line_threshold || 0,
558
+ total_workspaces: metricsFromDb.total_workspaces ?? workspaceConfig.totalWorkspaces ?? 42,
559
+ underperforming_workspaces: underperformingCount,
560
+ underperforming_workspace_names: metricsFromDb.underperforming_workspace_names || [],
561
+ underperforming_workspace_uuids: metricsFromDb.underperforming_workspace_uuids || [],
562
+ output_array: metricsFromDb.output_array || [],
563
+ line_threshold: metricsFromDb.line_threshold || 0,
564
+ threshold_pph: metricsFromDb.threshold_pph || 0,
565
+ shift_start: metricsFromDb.shift_start || shiftConfig.dayShift?.startTime || "06:00",
566
+ shift_end: metricsFromDb.shift_end || shiftConfig.dayShift?.endTime || "18:00",
567
+ last_updated: metricsFromDb.last_updated || (/* @__PURE__ */ new Date()).toISOString(),
568
+ poorest_performing_workspaces: metricsFromDb.poorest_performing_workspaces || []
569
+ };
570
+ if (metricsFromDb.efficiency === null || metricsFromDb.efficiency === void 0) {
571
+ metricsForReturn.avg_efficiency = avgEfficiencyFromPerf;
572
+ }
1071
573
  }
1072
- timeoutRef.current = setTimeout(() => {
1073
- fetchMetrics();
1074
- }, 500);
1075
- }, [fetchMetrics, workspaceId]);
1076
- const setupSubscription = useCallback(() => {
1077
- if (!workspaceId) return;
1078
- if (channelRef.current) {
1079
- supabase.removeChannel(channelRef.current);
574
+ return {
575
+ line_id: lineData.id,
576
+ line_name: lineData.line_name,
577
+ company_id: lineData.company_id,
578
+ company_name: lineData.companies?.[0]?.company_name ?? "",
579
+ factory_id: lineData.factory_id,
580
+ factory_name: lineData.factories?.[0]?.factory_name ?? "",
581
+ shift_id: shiftId,
582
+ date,
583
+ metrics: metricsForReturn
584
+ };
585
+ },
586
+ async getWorkspacesData(lineIdInput, dateProp, shiftProp) {
587
+ const supabase = _getSupabaseInstance();
588
+ const config = _getDashboardConfigInstance();
589
+ const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
590
+ const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
591
+ const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
592
+ const companyId = entityConfig.companyId;
593
+ if (!companyId) {
594
+ throw new Error("Company ID must be configured for workspace data requests.");
1080
595
  }
1081
- channelRef.current = supabase.channel("workspace-detailed-metrics").on(
1082
- "postgres_changes",
1083
- {
1084
- event: "*",
1085
- schema,
1086
- table: metricsTable,
1087
- filter: `workspace_id=eq.${workspaceId}`
1088
- },
1089
- async (payload) => {
1090
- console.log(`Received ${metricsTablePrefix} update:`, payload);
1091
- await fetchMetrics();
596
+ const metricsTablePrefixStr = getMetricsTablePrefix();
597
+ const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
598
+ const defaultLineId = entityConfig.defaultLineId;
599
+ const secondaryLineId = entityConfig.secondaryLineId;
600
+ const factoryViewId = entityConfig.factoryViewId ?? "factory";
601
+ const defaultTimezone = dateTimeConfig.defaultTimezone;
602
+ const currentShiftResult = getCurrentShift(defaultTimezone, shiftConfig);
603
+ const queryDate = dateProp || getOperationalDate(defaultTimezone);
604
+ const queryShiftId = shiftProp ?? currentShiftResult.shiftId;
605
+ const lineId = lineIdInput;
606
+ 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);
607
+ if (!lineId || lineId === factoryViewId) {
608
+ if (!defaultLineId || !secondaryLineId) {
609
+ throw new Error("Factory View requires defaultLineId and secondaryLineId to be configured for workspace data.");
1092
610
  }
1093
- ).subscribe((status) => {
1094
- console.log(`Workspace detailed metrics subscription status:`, status);
1095
- });
1096
- }, [supabase, workspaceId, fetchMetrics, schema, metricsTable, metricsTablePrefix]);
1097
- useEffect(() => {
1098
- if (!workspaceId) {
1099
- setIsLoading(false);
1100
- return;
611
+ query = query.in("line_id", [defaultLineId, secondaryLineId]);
612
+ } else {
613
+ query = query.eq("line_id", lineId);
1101
614
  }
1102
- const channels = [];
1103
- const operationalDate = date || getOperationalDate();
1104
- const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
1105
- const queryShiftId = shiftId ?? currentShift.shiftId;
1106
- const metricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
1107
- "postgres_changes",
1108
- {
1109
- event: "*",
1110
- schema,
1111
- table: metricsTable,
1112
- filter: `workspace_id=eq.${workspaceId}`
1113
- },
1114
- async (payload) => {
1115
- const payloadData = payload.new;
1116
- console.log(`Received ${metricsTablePrefix} update:`, {
1117
- payload,
1118
- payloadDate: payloadData?.date,
1119
- payloadShift: payloadData?.shift_id,
1120
- currentDate: operationalDate,
1121
- currentShift: queryShiftId,
1122
- matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
1123
- });
1124
- if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
1125
- queueUpdate();
1126
- }
1127
- }
1128
- ).subscribe((status) => {
1129
- console.log(`${metricsTablePrefix} subscription status:`, status);
1130
- });
1131
- const workspaceMetricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
1132
- "postgres_changes",
1133
- {
1134
- event: "*",
1135
- schema,
1136
- table: workspaceMetricsBaseTable,
1137
- filter: `workspace_id=eq.${workspaceId}`
1138
- },
1139
- async (payload) => {
1140
- const payloadData = payload.new;
1141
- console.log("Received workspace_metrics update:", {
1142
- payload,
1143
- payloadDate: payloadData?.date,
1144
- payloadShift: payloadData?.shift_id,
1145
- currentDate: operationalDate,
1146
- currentShift: queryShiftId,
1147
- matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
1148
- });
1149
- if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
1150
- queueUpdate();
1151
- }
1152
- }
1153
- ).subscribe((status) => {
1154
- console.log(`Workspace metrics subscription status:`, status);
1155
- });
1156
- const workspaceActionsChannel = supabase.channel(`workspace-actions-${workspaceId}`).on(
1157
- "postgres_changes",
1158
- {
1159
- event: "*",
1160
- schema,
1161
- table: workspaceActionsTable,
1162
- filter: `workspace_id=eq.${workspaceId}`
1163
- },
1164
- async (payload) => {
1165
- const payloadData = payload.new;
1166
- console.log("Received workspace_actions update:", {
1167
- payload,
1168
- payloadDate: payloadData?.date,
1169
- payloadShift: payloadData?.shift_id,
1170
- currentDate: operationalDate,
1171
- currentShift: queryShiftId,
1172
- matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
1173
- });
1174
- if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
1175
- queueUpdate();
1176
- }
1177
- }
1178
- ).subscribe((status) => {
1179
- console.log(`Workspace actions subscription status:`, status);
1180
- });
1181
- channels.push(metricsChannel, workspaceMetricsChannel, workspaceActionsChannel);
1182
- fetchMetrics();
1183
- setupSubscription();
1184
- return () => {
1185
- if (timeoutRef.current) {
1186
- clearTimeout(timeoutRef.current);
1187
- }
1188
- channels.forEach((channel) => {
1189
- console.log("Cleaning up channel subscription");
1190
- supabase.removeChannel(channel);
1191
- });
1192
- if (channelRef.current) {
1193
- supabase.removeChannel(channelRef.current);
1194
- }
1195
- };
1196
- }, [supabase, workspaceId, date, shiftId, fetchMetrics, queueUpdate, setupSubscription, metricsTable, workspaceMetricsBaseTable, workspaceActionsTable, defaultTimezone, shiftConfig, schema, metricsTablePrefix]);
1197
- return {
1198
- metrics: metrics2,
1199
- isLoading,
1200
- error,
1201
- refetch: fetchMetrics
1202
- };
1203
- };
1204
- var useLineWorkspaceMetrics = (lineId, options) => {
1205
- const entityConfig = useEntityConfig();
1206
- const databaseConfig = useDatabaseConfig();
1207
- const dateTimeConfig = useDateTimeConfig();
1208
- const shiftConfig = useShiftConfig();
1209
- const supabase = useSupabase();
1210
- const [workspaces, setWorkspaces] = useState([]);
1211
- const [loading, setLoading] = useState(true);
1212
- const [error, setError] = useState(null);
1213
- const [initialized, setInitialized] = useState(false);
1214
- const queryShiftId = useMemo(() => {
1215
- const currentShift = getCurrentShift(
1216
- dateTimeConfig.defaultTimezone || "Asia/Kolkata",
1217
- shiftConfig
1218
- );
1219
- return options?.initialShiftId !== void 0 ? options.initialShiftId : currentShift.shiftId;
1220
- }, [options?.initialShiftId, dateTimeConfig.defaultTimezone, shiftConfig]);
1221
- const queryDate = useMemo(() => {
1222
- return options?.initialDate || getOperationalDate(dateTimeConfig.defaultTimezone);
1223
- }, [options?.initialDate, dateTimeConfig.defaultTimezone]);
1224
- const metricsTable = useMemo(() => {
1225
- const companyId = entityConfig.companyId;
1226
- if (!companyId) return "";
1227
- const metricsTablePrefix = getMetricsTablePrefix();
1228
- return `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
1229
- }, [entityConfig.companyId]);
1230
- const schema = databaseConfig.schema ?? "public";
1231
- const fetchWorkspaceMetrics = useCallback(async () => {
1232
- if (!lineId) return;
1233
- if (!initialized) {
1234
- setLoading(true);
1235
- }
1236
- setError(null);
1237
- try {
1238
- console.log("Fetching workspace metrics with params:", {
1239
- lineId,
1240
- queryDate,
1241
- queryShiftId,
1242
- metricsTable
1243
- });
1244
- const { data, error: fetchError } = await supabase.from(metricsTable).select(`
1245
- workspace_name,
1246
- total_output,
1247
- avg_pph,
1248
- efficiency,
1249
- workspace_id,
1250
- avg_cycle_time,
1251
- performance_score,
1252
- trend_score,
1253
- line_id,
1254
- total_day_output
1255
- `).eq("date", queryDate).eq("shift_id", queryShiftId).eq("line_id", lineId).order("workspace_name", { ascending: true });
1256
- if (fetchError) throw fetchError;
1257
- const transformedData = (data || []).map((item) => ({
1258
- company_id: entityConfig.companyId || "unknown",
1259
- line_id: item.line_id,
1260
- shift_id: queryShiftId,
1261
- date: queryDate,
1262
- workspace_uuid: item.workspace_id,
1263
- workspace_name: item.workspace_name,
1264
- action_count: item.total_output || 0,
1265
- pph: item.avg_pph || 0,
1266
- performance_score: item.performance_score || 0,
1267
- avg_cycle_time: item.avg_cycle_time || 0,
1268
- trend: item.trend_score === 1 ? 2 : 0,
1269
- predicted_output: 0,
1270
- efficiency: item.efficiency || 0,
1271
- action_threshold: item.total_day_output || 0
1272
- }));
1273
- setWorkspaces(transformedData);
1274
- setInitialized(true);
1275
- } catch (err) {
1276
- console.error("Error fetching workspace metrics:", err);
1277
- setError({ message: err.message, code: err.code || "FETCH_ERROR" });
1278
- } finally {
1279
- setLoading(false);
1280
- }
1281
- }, [lineId, queryDate, queryShiftId, metricsTable, supabase, entityConfig.companyId]);
1282
- useEffect(() => {
1283
- if (!initialized) {
1284
- fetchWorkspaceMetrics();
615
+ const { data, error } = await query;
616
+ if (error) {
617
+ console.error("Error in getWorkspacesData:", error);
618
+ throw error;
1285
619
  }
1286
- const setupSubscription = () => {
1287
- if (!lineId) return null;
1288
- const filter2 = `line_id=eq.${lineId} AND date=eq.${queryDate} AND shift_id=eq.${queryShiftId}`;
1289
- console.log("Setting up subscription with filter:", filter2);
1290
- const channel2 = supabase.channel(`line-workspace-metrics-${Date.now()}`).on(
1291
- "postgres_changes",
1292
- {
1293
- event: "*",
1294
- schema,
1295
- table: metricsTable,
1296
- filter: filter2
1297
- },
1298
- async (payload) => {
1299
- console.log("Workspace metrics update received:", payload);
1300
- await fetchWorkspaceMetrics();
1301
- }
1302
- ).subscribe();
1303
- return channel2;
1304
- };
1305
- const channel = setupSubscription();
1306
- return () => {
1307
- if (channel) {
1308
- supabase.removeChannel(channel);
1309
- }
1310
- };
1311
- }, [lineId, queryDate, queryShiftId, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema]);
1312
- useEffect(() => {
1313
- setInitialized(false);
1314
- }, [lineId, queryDate, queryShiftId]);
1315
- const refreshWorkspaces = fetchWorkspaceMetrics;
1316
- return useMemo(
1317
- () => ({ workspaces, loading, error, refreshWorkspaces }),
1318
- [workspaces, loading, error, refreshWorkspaces]
1319
- );
1320
- };
1321
-
1322
- // src/lib/services/dashboardService.ts
1323
- var getTable = (dbConfig, tableName) => {
1324
- const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
1325
- const userValue = dbConfig?.tables?.[tableName];
1326
- return userValue ?? defaults2[tableName];
1327
- };
1328
- var dashboardService = {
1329
- // Example for getLineInfo:
1330
- async getLineInfo(lineIdInput) {
620
+ return (data || []).map((item) => ({
621
+ company_id: item.company_id,
622
+ line_id: item.line_id,
623
+ shift_id: item.shift_id,
624
+ date: item.date,
625
+ workspace_uuid: item.workspace_id,
626
+ workspace_name: item.workspace_name,
627
+ action_count: item.total_output || 0,
628
+ pph: item.avg_pph || 0,
629
+ performance_score: item.performance_score || 0,
630
+ avg_cycle_time: item.avg_cycle_time || 0,
631
+ trend: item.trend_score === 1 ? 2 : item.trend_score === 0 ? 0 : 1,
632
+ predicted_output: item.ideal_output || 0,
633
+ efficiency: item.efficiency || 0,
634
+ action_threshold: item.total_day_output || 0
635
+ }));
636
+ },
637
+ async getWorkspaceDetailedMetrics(workspaceUuid, dateProp, shiftIdProp) {
1331
638
  const supabase = _getSupabaseInstance();
1332
639
  const config = _getDashboardConfigInstance();
1333
- const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
1334
640
  const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
1335
641
  const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
1336
642
  const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
1337
643
  const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
1338
- const linesTable = getTable(dbConfig, "lines");
1339
- const lineMetricsTable = getTable(dbConfig, "lineMetrics");
1340
644
  const companyId = entityConfig.companyId;
645
+ if (!companyId) {
646
+ throw new Error("Company ID must be configured for detailed workspace metrics.");
647
+ }
1341
648
  const metricsTablePrefixStr = getMetricsTablePrefix();
1342
- const metricsTable = `${metricsTablePrefixStr}_${companyId ? companyId.replace(/-/g, "_") : "unknown_company"}`;
1343
- const defaultLineId = entityConfig.defaultLineId;
1344
- const secondaryLineId = entityConfig.secondaryLineId;
1345
- const factoryViewId = entityConfig.factoryViewId ?? "factory";
1346
- const defaultTimezone = dateTimeConfig.defaultTimezone;
1347
- const { shiftId, date } = getCurrentShift(defaultTimezone, shiftConfig);
1348
- const lineId = lineIdInput;
1349
- if (lineId === factoryViewId) {
1350
- if (!defaultLineId || !secondaryLineId || !companyId) {
1351
- throw new Error("Factory View requires defaultLineId, secondaryLineId, and companyId to be configured.");
1352
- }
1353
- const { data: lineData2, error: lineError2 } = await supabase.from(linesTable).select(`
1354
- id,
1355
- line_name,
1356
- factory_id,
1357
- factories!lines_factory_id_fkey(factory_name),
1358
- company_id,
1359
- companies!lines_company_id_fkey(company_name:name)
1360
- `).eq("id", defaultLineId).maybeSingle();
1361
- if (lineError2) throw lineError2;
1362
- if (!lineData2) throw new Error(`Configured default line (${defaultLineId}) not found`);
1363
- const { data: metricsData, error: metricsError } = await supabase.from(lineMetricsTable).select("*").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
1364
- if (metricsError) throw metricsError;
1365
- const { data: performanceData2, error: performanceError2 } = await supabase.from(metricsTable).select("efficiency").in("line_id", [defaultLineId, secondaryLineId]).eq("shift_id", shiftId).eq("date", date);
1366
- if (performanceError2) throw performanceError2;
1367
- const underperformingCount2 = performanceData2?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
1368
- const totalValidWorkspaces2 = performanceData2?.filter((w) => w.efficiency >= 10).length || 0;
1369
- const combinedMetrics = (metricsData || []).reduce((acc, m) => {
1370
- acc.avg_efficiency += m.efficiency ?? 0;
1371
- acc.current_output += m.current_output ?? 0;
1372
- acc.ideal_output += m.ideal_output ?? m.line_threshold ?? 0;
1373
- return acc;
1374
- }, { avg_efficiency: 0, current_output: 0, ideal_output: 0 });
1375
- metricsData?.length || 1;
1376
- return {
1377
- line_id: factoryViewId,
1378
- // Use configured factory view ID
1379
- line_name: "Factory View",
1380
- // Consider making this configurable?
1381
- company_id: lineData2.company_id,
1382
- company_name: lineData2.companies?.[0]?.company_name ?? "",
1383
- factory_id: lineData2.factory_id,
1384
- factory_name: lineData2.factories?.[0]?.factory_name ?? "",
1385
- shift_id: shiftId,
1386
- date,
1387
- metrics: {
1388
- avg_efficiency: (performanceData2?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces2 || 1),
1389
- // Use performance data for avg efficiency
1390
- avg_cycle_time: 0,
1391
- // Needs calculation logic if required for factory view
1392
- current_output: combinedMetrics.current_output,
1393
- ideal_output: combinedMetrics.ideal_output,
1394
- total_workspaces: 44,
1395
- // SRC ALIGNMENT: Use hardcoded 44 for factory view total_workspaces
1396
- underperforming_workspaces: underperformingCount2,
1397
- underperforming_workspace_names: [],
1398
- // Populate if needed
1399
- underperforming_workspace_uuids: [],
1400
- // Populate if needed
1401
- output_array: [],
1402
- // Combine if needed
1403
- line_threshold: combinedMetrics.ideal_output,
1404
- threshold_pph: 0,
1405
- // Needs calculation logic if required
1406
- shift_start: shiftConfig.dayShift?.startTime || "06:00",
1407
- // Use config
1408
- shift_end: shiftConfig.dayShift?.endTime || "18:00",
1409
- // Use config
1410
- last_updated: (/* @__PURE__ */ new Date()).toISOString(),
1411
- poorest_performing_workspaces: []
1412
- // Populate if needed
1413
- }
1414
- };
1415
- }
1416
- if (!companyId) {
1417
- throw new Error("Company ID must be configured for individual line requests.");
1418
- }
1419
- const { data: lineData, error: lineError } = await supabase.from(linesTable).select("id, line_name, factory_id, factories!lines_factory_id_fkey(factory_name), company_id, companies!lines_company_id_fkey(company_name:name)").eq("id", lineId).maybeSingle();
1420
- if (lineError) throw lineError;
1421
- if (!lineData) throw new Error(`Line with ID ${lineId} not found`);
1422
- let metricsFromDb = null;
1423
- try {
1424
- const { data: fetchedMetrics, error } = await supabase.from(lineMetricsTable).select("*").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date).maybeSingle();
1425
- if (error) throw error;
1426
- metricsFromDb = fetchedMetrics;
1427
- } catch (err) {
1428
- console.error(`Error fetching line metrics for ${lineId}:`, err);
1429
- }
1430
- const { data: performanceData, error: performanceError } = await supabase.from(metricsTable).select("efficiency").eq("line_id", lineId).eq("shift_id", shiftId).eq("date", date);
1431
- if (performanceError) throw performanceError;
1432
- const underperformingCount = performanceData?.filter((w) => w.efficiency >= 10 && w.efficiency < 70).length || 0;
1433
- const totalValidWorkspaces = performanceData?.filter((w) => w.efficiency >= 10).length || 0;
1434
- const avgEfficiencyFromPerf = (performanceData?.reduce((sum, m) => sum + (m.efficiency || 0), 0) || 0) / (totalValidWorkspaces || 1);
1435
- const useFallbackMetrics = !metricsFromDb;
1436
- let metricsForReturn;
1437
- if (useFallbackMetrics) {
1438
- metricsForReturn = {
1439
- avg_efficiency: 0,
1440
- avg_cycle_time: 0,
1441
- current_output: 0,
1442
- ideal_output: 0,
1443
- total_workspaces: 42,
1444
- underperforming_workspaces: underperformingCount,
1445
- underperforming_workspace_names: [],
1446
- underperforming_workspace_uuids: [],
1447
- output_array: [],
1448
- line_threshold: 0,
1449
- threshold_pph: 0,
1450
- shift_start: "06:00",
1451
- shift_end: "14:00",
1452
- last_updated: (/* @__PURE__ */ new Date()).toISOString(),
1453
- poorest_performing_workspaces: []
1454
- };
1455
- } else {
1456
- metricsForReturn = {
1457
- avg_efficiency: metricsFromDb.efficiency ?? avgEfficiencyFromPerf,
1458
- avg_cycle_time: metricsFromDb.avg_cycle_time || 0,
1459
- current_output: metricsFromDb.current_output || 0,
1460
- ideal_output: metricsFromDb.ideal_output || metricsFromDb.line_threshold || 0,
1461
- total_workspaces: metricsFromDb.total_workspaces ?? workspaceConfig.totalWorkspaces ?? 42,
1462
- underperforming_workspaces: underperformingCount,
1463
- underperforming_workspace_names: metricsFromDb.underperforming_workspace_names || [],
1464
- underperforming_workspace_uuids: metricsFromDb.underperforming_workspace_uuids || [],
1465
- output_array: metricsFromDb.output_array || [],
1466
- line_threshold: metricsFromDb.line_threshold || 0,
1467
- threshold_pph: metricsFromDb.threshold_pph || 0,
1468
- shift_start: metricsFromDb.shift_start || shiftConfig.dayShift?.startTime || "06:00",
1469
- shift_end: metricsFromDb.shift_end || shiftConfig.dayShift?.endTime || "18:00",
1470
- last_updated: metricsFromDb.last_updated || (/* @__PURE__ */ new Date()).toISOString(),
1471
- poorest_performing_workspaces: metricsFromDb.poorest_performing_workspaces || []
1472
- };
1473
- if (metricsFromDb.efficiency === null || metricsFromDb.efficiency === void 0) {
1474
- metricsForReturn.avg_efficiency = avgEfficiencyFromPerf;
1475
- }
1476
- }
1477
- return {
1478
- line_id: lineData.id,
1479
- line_name: lineData.line_name,
1480
- company_id: lineData.company_id,
1481
- company_name: lineData.companies?.[0]?.company_name ?? "",
1482
- factory_id: lineData.factory_id,
1483
- factory_name: lineData.factories?.[0]?.factory_name ?? "",
1484
- shift_id: shiftId,
1485
- date,
1486
- metrics: metricsForReturn
1487
- };
1488
- },
1489
- async getWorkspacesData(lineIdInput, dateProp, shiftProp) {
1490
- const supabase = _getSupabaseInstance();
1491
- const config = _getDashboardConfigInstance();
1492
- const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
1493
- const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
1494
- const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
1495
- const companyId = entityConfig.companyId;
1496
- if (!companyId) {
1497
- throw new Error("Company ID must be configured for workspace data requests.");
1498
- }
1499
- const metricsTablePrefixStr = getMetricsTablePrefix();
1500
- const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
1501
- const defaultLineId = entityConfig.defaultLineId;
1502
- const secondaryLineId = entityConfig.secondaryLineId;
1503
- const factoryViewId = entityConfig.factoryViewId ?? "factory";
1504
- const defaultTimezone = dateTimeConfig.defaultTimezone;
1505
- const currentShiftResult = getCurrentShift(defaultTimezone, shiftConfig);
1506
- const queryDate = dateProp || getOperationalDate(defaultTimezone);
1507
- const queryShiftId = shiftProp ?? currentShiftResult.shiftId;
1508
- const lineId = lineIdInput;
1509
- let query = supabase.from(metricsTable).select("company_id,line_id,shift_id,date,workspace_id,workspace_name,total_output,avg_pph,performance_score,avg_cycle_time,trend_score,ideal_output,efficiency,total_day_output").eq("shift_id", queryShiftId).eq("date", queryDate);
1510
- if (!lineId || lineId === factoryViewId) {
1511
- if (!defaultLineId || !secondaryLineId) {
1512
- throw new Error("Factory View requires defaultLineId and secondaryLineId to be configured for workspace data.");
1513
- }
1514
- query = query.in("line_id", [defaultLineId, secondaryLineId]);
1515
- } else {
1516
- query = query.eq("line_id", lineId);
1517
- }
1518
- const { data, error } = await query;
1519
- if (error) {
1520
- console.error("Error in getWorkspacesData:", error);
1521
- throw error;
1522
- }
1523
- return (data || []).map((item) => ({
1524
- company_id: item.company_id,
1525
- line_id: item.line_id,
1526
- shift_id: item.shift_id,
1527
- date: item.date,
1528
- workspace_uuid: item.workspace_id,
1529
- workspace_name: item.workspace_name,
1530
- action_count: item.total_output || 0,
1531
- pph: item.avg_pph || 0,
1532
- performance_score: item.performance_score || 0,
1533
- avg_cycle_time: item.avg_cycle_time || 0,
1534
- trend: item.trend_score === 1 ? 2 : item.trend_score === 0 ? 0 : 1,
1535
- predicted_output: item.ideal_output || 0,
1536
- efficiency: item.efficiency || 0,
1537
- action_threshold: item.total_day_output || 0
1538
- }));
1539
- },
1540
- async getWorkspaceDetailedMetrics(workspaceUuid, dateProp, shiftIdProp) {
1541
- const supabase = _getSupabaseInstance();
1542
- const config = _getDashboardConfigInstance();
1543
- const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
1544
- const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
1545
- const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
1546
- const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
1547
- const companyId = entityConfig.companyId;
1548
- if (!companyId) {
1549
- throw new Error("Company ID must be configured for detailed workspace metrics.");
1550
- }
1551
- const metricsTablePrefixStr = getMetricsTablePrefix();
1552
- const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
649
+ const metricsTable = `${metricsTablePrefixStr}_${companyId.replace(/-/g, "_")}`;
1553
650
  const defaultTimezone = dateTimeConfig.defaultTimezone;
1554
651
  const currentShiftResult = getCurrentShift(defaultTimezone, shiftConfig);
1555
652
  const queryDate = dateProp || getOperationalDate(defaultTimezone);
@@ -1689,7 +786,7 @@ var dashboardService = {
1689
786
  const supabase = _getSupabaseInstance();
1690
787
  const config = _getDashboardConfigInstance();
1691
788
  const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
1692
- const linesTable = getTable(dbConfig, "lines");
789
+ const linesTable = getTable2(dbConfig, "lines");
1693
790
  const companyId = config.entityConfig?.companyId;
1694
791
  try {
1695
792
  let query = supabase.from(linesTable).select(`
@@ -1731,8 +828,8 @@ var dashboardService = {
1731
828
  const shiftConfig = config.shiftConfig ?? DEFAULT_SHIFT_CONFIG;
1732
829
  const dateTimeConfig = config.dateTimeConfig ?? DEFAULT_DATE_TIME_CONFIG;
1733
830
  const workspaceConfig = config.workspaceConfig ?? DEFAULT_WORKSPACE_CONFIG;
1734
- const linesTable = getTable(dbConfig, "lines");
1735
- const lineMetricsTable = getTable(dbConfig, "lineMetrics");
831
+ const linesTable = getTable2(dbConfig, "lines");
832
+ const lineMetricsTable = getTable2(dbConfig, "lineMetrics");
1736
833
  const companyId = entityConfig.companyId;
1737
834
  const metricsTablePrefixStr = getMetricsTablePrefix();
1738
835
  const metricsTable = `${metricsTablePrefixStr}_${companyId ? companyId.replace(/-/g, "_") : "unknown_company"}`;
@@ -1883,7 +980,7 @@ var dashboardService = {
1883
980
  const formattedStartDate = formatDate(startDate);
1884
981
  const formattedEndDate = formatDate(endDate);
1885
982
  try {
1886
- const { data, error } = await supabase.from(metricsTable).select("date, shift_id, efficiency, total_output, avg_cycle_time, ideal_output, avg_pph, pph_threshold, workspace_rank").eq("workspace_id", workspaceUuid).gte("date", formattedStartDate).lte("date", formattedEndDate).order("date", { ascending: true }).order("shift_id", { ascending: true });
983
+ const { data, error } = await supabase.from(metricsTable).select("date, shift_id, efficiency, total_output, avg_cycle_time, idle_time, ideal_output, avg_pph, pph_threshold, workspace_rank").eq("workspace_id", workspaceUuid).gte("date", formattedStartDate).lte("date", formattedEndDate).order("date", { ascending: true }).order("shift_id", { ascending: true });
1887
984
  if (error) throw error;
1888
985
  if (!data) return [];
1889
986
  const transformedData = data.map((item) => ({
@@ -1896,7 +993,8 @@ var dashboardService = {
1896
993
  ideal_output: item.ideal_output || 0,
1897
994
  avg_pph: item.avg_pph || 0,
1898
995
  pph_threshold: item.pph_threshold || 0,
1899
- workspace_rank: item.workspace_rank || 0
996
+ workspace_rank: item.workspace_rank || 0,
997
+ idle_time: item.idle_time || 0
1900
998
  }));
1901
999
  return transformedData;
1902
1000
  } catch (err) {
@@ -1909,7 +1007,7 @@ var dashboardService = {
1909
1007
  const config = _getDashboardConfigInstance();
1910
1008
  const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
1911
1009
  const entityConfig = config.entityConfig ?? DEFAULT_ENTITY_CONFIG;
1912
- const lineMetricsTable = getTable(dbConfig, "lineMetrics");
1010
+ const lineMetricsTable = getTable2(dbConfig, "lineMetrics");
1913
1011
  const defaultLineId = entityConfig.defaultLineId;
1914
1012
  const secondaryLineId = entityConfig.secondaryLineId;
1915
1013
  const factoryViewId = entityConfig.factoryViewId ?? "factory";
@@ -1996,162 +1094,35 @@ var dashboardService = {
1996
1094
  }
1997
1095
  };
1998
1096
 
1999
- // src/lib/hooks/useHistoricWorkspaceMetrics.ts
2000
- var useHistoricWorkspaceMetrics = (workspaceId, date, inputShiftId) => {
2001
- useSupabase();
2002
- const [metrics2, setMetrics] = useState(null);
2003
- const [isLoading, setIsLoading] = useState(true);
2004
- const [error, setError] = useState(null);
2005
- const fetchAndAnimateMetrics = useCallback(async () => {
2006
- if (!workspaceId || !date || inputShiftId === void 0) {
2007
- setMetrics(null);
2008
- setIsLoading(false);
2009
- setError(null);
2010
- return;
2011
- }
2012
- setIsLoading(true);
2013
- setError(null);
2014
- try {
2015
- const fetchedData = await dashboardService.getWorkspaceDetailedMetrics(
2016
- workspaceId,
2017
- date,
2018
- inputShiftId
2019
- );
2020
- if (!fetchedData) {
2021
- console.warn("No historic data found for workspace:", workspaceId, date, inputShiftId);
2022
- setMetrics(null);
2023
- setIsLoading(false);
2024
- return;
2025
- }
2026
- const initialHourlyCounts = fetchedData.hourly_action_counts && Array.isArray(fetchedData.hourly_action_counts) ? new Array(fetchedData.hourly_action_counts.length).fill(0) : [];
2027
- setMetrics({
2028
- ...fetchedData,
2029
- hourly_action_counts: initialHourlyCounts
2030
- });
2031
- setIsLoading(false);
2032
- if (fetchedData.hourly_action_counts && fetchedData.hourly_action_counts.length > 0) {
2033
- const totalSteps = 60;
2034
- let currentStep = 0;
2035
- let animationIntervalId = setInterval(() => {
2036
- currentStep++;
2037
- const progress6 = currentStep / totalSteps;
2038
- setMetrics((prevMetrics) => {
2039
- const currentHourlyData = (fetchedData.hourly_action_counts || []).map(
2040
- (target) => Math.round(target * progress6)
2041
- );
2042
- return {
2043
- ...fetchedData,
2044
- // Base with all other correct data from the latest fetch
2045
- hourly_action_counts: currentHourlyData
2046
- };
2047
- });
2048
- if (currentStep >= totalSteps) {
2049
- if (animationIntervalId) clearInterval(animationIntervalId);
2050
- setMetrics(fetchedData);
2051
- }
2052
- }, 1e3 / 60);
2053
- } else {
2054
- setMetrics(fetchedData);
2055
- }
2056
- } catch (err) {
2057
- console.error("Error fetching historic workspace metrics:", err);
2058
- setError({ message: err.message, code: err.code || "FETCH_ERROR" });
2059
- setIsLoading(false);
2060
- setMetrics(null);
2061
- }
2062
- }, [workspaceId, date, inputShiftId]);
2063
- useEffect(() => {
2064
- fetchAndAnimateMetrics();
2065
- }, [fetchAndAnimateMetrics]);
2066
- const refetch = useCallback(async () => {
2067
- if (!workspaceId || !date || inputShiftId === void 0) {
2068
- setError(null);
2069
- return;
2070
- }
2071
- setIsLoading(true);
2072
- setError(null);
2073
- try {
2074
- const fetchedData = await dashboardService.getWorkspaceDetailedMetrics(
2075
- workspaceId,
2076
- date,
2077
- inputShiftId
2078
- );
2079
- if (!fetchedData) {
2080
- setMetrics(null);
2081
- return;
2082
- }
2083
- setMetrics(fetchedData);
2084
- } catch (err) {
2085
- console.error("Error re-fetching historic workspace metrics:", err);
2086
- setError({ message: err.message, code: err.code || "FETCH_ERROR" });
2087
- setMetrics(null);
2088
- } finally {
2089
- setIsLoading(false);
2090
- }
2091
- }, [workspaceId, date, inputShiftId]);
2092
- return {
2093
- metrics: metrics2,
2094
- isLoading,
2095
- error,
2096
- refetch
2097
- };
2098
- };
2099
-
2100
- // src/lib/services/actionService.ts
2101
- var getTable2 = (dbConfig, tableName) => {
2102
- const defaults2 = DEFAULT_DATABASE_CONFIG.tables;
2103
- const userValue = dbConfig?.tables?.[tableName];
2104
- return userValue ?? defaults2[tableName];
2105
- };
2106
- var actionService = {
2107
- async getActionsByName(actionNames, companyIdInput) {
2108
- const supabase = _getSupabaseInstance();
2109
- const config = _getDashboardConfigInstance();
2110
- const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
2111
- const entityConfig = config.entityConfig;
2112
- const actionsTable = getTable2(dbConfig, "actions");
2113
- const targetCompanyId = companyIdInput ?? entityConfig?.companyId;
2114
- if (!targetCompanyId) {
2115
- throw new Error("Company ID must be provided either via entityConfig.companyId or as an argument to getActionsByName.");
2116
- }
2117
- const { data, error } = await supabase.from(actionsTable).select("id, action_name, company_id").eq("company_id", targetCompanyId).in("action_name", actionNames);
2118
- if (error) {
2119
- console.error(`Error fetching actions from table ${actionsTable}:`, error);
2120
- throw error;
2121
- }
2122
- return data || [];
2123
- }
2124
- };
2125
-
2126
- // src/lib/services/realtimeService.ts
2127
- function isValidLineInfoPayload(payload) {
2128
- return payload && typeof payload === "object" && "line_id" in payload;
2129
- }
2130
- function isValidWorkspaceMetricsPayload(payload) {
2131
- return payload && typeof payload === "object" && "workspace_uuid" in payload;
2132
- }
2133
- function isValidWorkspaceDetailedMetricsPayload(payload) {
2134
- return payload && typeof payload === "object" && "workspace_id" in payload && "hourly_action_counts" in payload;
2135
- }
2136
- var realtimeService = {
2137
- subscribeToLineInfo(lineId, shiftId, date, onDataUpdate) {
2138
- const supabase = _getSupabaseInstance();
2139
- const config = _getDashboardConfigInstance();
2140
- const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
2141
- const schema = dbConfig.schema ?? "public";
2142
- const channelName = `line_info_${lineId}_${shiftId}_${date}`;
2143
- const TABLE_NAME = "line_info";
2144
- const channel = supabase.channel(channelName);
2145
- channel.on(
2146
- "postgres_changes",
2147
- { event: "*", schema, table: TABLE_NAME, filter: `line_id=eq.${lineId}` },
2148
- (payload) => {
2149
- const record = payload.new ?? payload.old;
2150
- if (record && isValidLineInfoPayload(record)) {
2151
- if ((!record.shift_id || record.shift_id === shiftId) && (!record.date || record.date === date)) {
2152
- onDataUpdate(record);
2153
- }
2154
- }
1097
+ // src/lib/services/realtimeService.ts
1098
+ function isValidLineInfoPayload(payload) {
1099
+ return payload && typeof payload === "object" && "line_id" in payload;
1100
+ }
1101
+ function isValidWorkspaceMetricsPayload(payload) {
1102
+ return payload && typeof payload === "object" && "workspace_uuid" in payload;
1103
+ }
1104
+ function isValidWorkspaceDetailedMetricsPayload(payload) {
1105
+ return payload && typeof payload === "object" && "workspace_id" in payload && "hourly_action_counts" in payload;
1106
+ }
1107
+ var realtimeService = {
1108
+ subscribeToLineInfo(lineId, shiftId, date, onDataUpdate) {
1109
+ const supabase = _getSupabaseInstance();
1110
+ const config = _getDashboardConfigInstance();
1111
+ const dbConfig = config.databaseConfig ?? DEFAULT_DATABASE_CONFIG;
1112
+ const schema = dbConfig.schema ?? "public";
1113
+ const channelName = `line_info_${lineId}_${shiftId}_${date}`;
1114
+ const TABLE_NAME = "line_info";
1115
+ const channel = supabase.channel(channelName);
1116
+ channel.on(
1117
+ "postgres_changes",
1118
+ { event: "*", schema, table: TABLE_NAME, filter: `line_id=eq.${lineId}` },
1119
+ (payload) => {
1120
+ const record = payload.new ?? payload.old;
1121
+ if (record && isValidLineInfoPayload(record)) {
1122
+ if ((!record.shift_id || record.shift_id === shiftId) && (!record.date || record.date === date)) {
1123
+ onDataUpdate(record);
1124
+ }
1125
+ }
2155
1126
  }
2156
1127
  ).subscribe((status, err) => {
2157
1128
  if (err) console.error(`[RealtimeService] Line info subscription error for ${channelName}:`, err);
@@ -2824,6 +1795,7 @@ var authRateLimitService = {
2824
1795
  clearAllRateLimits
2825
1796
  };
2826
1797
  var isMixpanelInitialized = false;
1798
+ var currentUserProperties;
2827
1799
  var initializeCoreMixpanel = (token, debug, trackPageView) => {
2828
1800
  if (!token) {
2829
1801
  console.warn("Mixpanel token not provided for initialization. Mixpanel will not be enabled.");
@@ -2849,7 +1821,13 @@ var trackCorePageView = (pageName, properties) => {
2849
1821
  };
2850
1822
  var trackCoreEvent = (eventName, properties) => {
2851
1823
  if (!isMixpanelInitialized) return;
2852
- mixpanel.track(eventName, properties);
1824
+ const mergedProps = {
1825
+ // Precedence order: explicit properties passed by caller should override
1826
+ // automatically appended user properties to avoid accidental overwrites.
1827
+ ...currentUserProperties || {},
1828
+ ...properties || {}
1829
+ };
1830
+ mixpanel.track(eventName, mergedProps);
2853
1831
  };
2854
1832
  var identifyCoreUser = (userId, userProperties) => {
2855
1833
  if (!isMixpanelInitialized) return;
@@ -2857,6 +1835,7 @@ var identifyCoreUser = (userId, userProperties) => {
2857
1835
  if (userProperties) {
2858
1836
  mixpanel.people.set(userProperties);
2859
1837
  }
1838
+ currentUserProperties = { ...userProperties };
2860
1839
  };
2861
1840
  var resetCoreMixpanel = () => {
2862
1841
  if (!isMixpanelInitialized) return;
@@ -2924,150 +1903,1194 @@ var SSEChatClient = class {
2924
1903
  } catch (textError) {
2925
1904
  }
2926
1905
  }
2927
- console.error("[SSEClient] Error response:", errorMessage);
2928
- throw new Error(errorMessage);
2929
- }
2930
- const contentType = response.headers.get("content-type");
2931
- if (!contentType?.includes("text/event-stream")) {
2932
- console.warn("[SSEClient] Unexpected content-type:", contentType);
1906
+ console.error("[SSEClient] Error response:", errorMessage);
1907
+ throw new Error(errorMessage);
1908
+ }
1909
+ const contentType = response.headers.get("content-type");
1910
+ if (!contentType?.includes("text/event-stream")) {
1911
+ console.warn("[SSEClient] Unexpected content-type:", contentType);
1912
+ }
1913
+ try {
1914
+ await this.handleSSEStream(response, callbacks);
1915
+ } finally {
1916
+ this.controllers.delete(connectionId);
1917
+ }
1918
+ }
1919
+ async handleSSEStream(response, callbacks) {
1920
+ if (!response.body) {
1921
+ console.error("[SSEClient] Response body is null");
1922
+ throw new Error("No response body available for streaming");
1923
+ }
1924
+ const reader = response.body.getReader();
1925
+ const decoder = new TextDecoder();
1926
+ let buffer = "";
1927
+ try {
1928
+ console.log("[SSEClient] Starting to read stream...");
1929
+ while (true) {
1930
+ const { done, value } = await reader.read();
1931
+ if (done) {
1932
+ console.log("[SSEClient] Stream ended");
1933
+ break;
1934
+ }
1935
+ buffer += decoder.decode(value, { stream: true });
1936
+ const lines = buffer.split("\n");
1937
+ buffer = lines.pop() || "";
1938
+ for (let i = 0; i < lines.length; i++) {
1939
+ const line = lines[i].trim();
1940
+ if (!line) continue;
1941
+ console.log("[SSEClient] Processing line:", line);
1942
+ if (line.startsWith("event:")) {
1943
+ const event = line.slice(6).trim();
1944
+ console.log("[SSEClient] Event type:", event);
1945
+ const nextLine = lines[i + 1];
1946
+ if (nextLine?.startsWith("data:")) {
1947
+ const dataStr = nextLine.slice(5).trim();
1948
+ console.log("[SSEClient] Event data:", dataStr);
1949
+ try {
1950
+ const data = JSON.parse(dataStr);
1951
+ switch (event) {
1952
+ case "thread":
1953
+ callbacks.onThreadCreated?.(data.thread_id);
1954
+ break;
1955
+ case "message":
1956
+ callbacks.onMessage?.(data.text);
1957
+ break;
1958
+ case "reasoning":
1959
+ callbacks.onReasoning?.(data.text);
1960
+ break;
1961
+ case "complete":
1962
+ callbacks.onComplete?.(data.message_id);
1963
+ break;
1964
+ case "error":
1965
+ callbacks.onError?.(data.error);
1966
+ break;
1967
+ }
1968
+ } catch (e) {
1969
+ console.error("[SSEClient] Failed to parse data:", dataStr, e);
1970
+ }
1971
+ i++;
1972
+ }
1973
+ }
1974
+ }
1975
+ }
1976
+ } finally {
1977
+ reader.releaseLock();
1978
+ }
1979
+ }
1980
+ abort(threadId) {
1981
+ if (threadId) {
1982
+ for (const [id3, controller] of this.controllers.entries()) {
1983
+ if (id3.startsWith(threadId)) {
1984
+ controller.abort();
1985
+ this.controllers.delete(id3);
1986
+ }
1987
+ }
1988
+ } else {
1989
+ for (const [id3, controller] of this.controllers.entries()) {
1990
+ controller.abort();
1991
+ }
1992
+ this.controllers.clear();
1993
+ }
1994
+ }
1995
+ };
1996
+
1997
+ // src/lib/services/chatService.ts
1998
+ async function getUserThreads(userId, limit = 20) {
1999
+ const supabase = _getSupabaseInstance();
2000
+ const { data, error } = await supabase.schema("ai").from("chat_threads").select("*").eq("user_id", userId).order("updated_at", { ascending: false }).limit(limit);
2001
+ if (error) throw error;
2002
+ return data;
2003
+ }
2004
+ async function getUserThreadsPaginated(userId, page = 1, pageSize = 20) {
2005
+ const supabase = _getSupabaseInstance();
2006
+ const from = (page - 1) * pageSize;
2007
+ const to = from + pageSize - 1;
2008
+ 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);
2009
+ if (error) throw error;
2010
+ return {
2011
+ threads: data,
2012
+ totalCount: count || 0,
2013
+ totalPages: Math.ceil((count || 0) / pageSize),
2014
+ currentPage: page
2015
+ };
2016
+ }
2017
+ async function getThreadMessages(threadId, limit = 50, beforePosition) {
2018
+ const supabase = _getSupabaseInstance();
2019
+ let query = supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
2020
+ if (beforePosition !== void 0) {
2021
+ query = query.lt("position", beforePosition);
2022
+ }
2023
+ const { data, error } = await query.limit(limit);
2024
+ if (error) throw error;
2025
+ return data;
2026
+ }
2027
+ async function getAllThreadMessages(threadId) {
2028
+ const supabase = _getSupabaseInstance();
2029
+ const { data, error } = await supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
2030
+ if (error) throw error;
2031
+ return data;
2032
+ }
2033
+ async function updateThreadTitle(threadId, newTitle) {
2034
+ const supabase = _getSupabaseInstance();
2035
+ const { data, error } = await supabase.schema("ai").from("chat_threads").update({
2036
+ title: newTitle,
2037
+ auto_title: false,
2038
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
2039
+ }).eq("id", threadId).select().single();
2040
+ if (error) throw error;
2041
+ return data;
2042
+ }
2043
+ async function deleteThread(threadId) {
2044
+ const supabase = _getSupabaseInstance();
2045
+ const { error } = await supabase.schema("ai").from("chat_threads").delete().eq("id", threadId);
2046
+ if (error) throw error;
2047
+ }
2048
+ var AuthContext = createContext({
2049
+ session: null,
2050
+ user: null,
2051
+ loading: true,
2052
+ error: null,
2053
+ signOut: async () => {
2054
+ }
2055
+ });
2056
+ var useAuth = () => useContext(AuthContext);
2057
+ var AuthProvider = ({ children }) => {
2058
+ const supabase = useSupabase();
2059
+ const { authConfig } = useDashboardConfig();
2060
+ const [session, setSession] = useState(null);
2061
+ const [user, setUser] = useState(null);
2062
+ const [loading, setLoading] = useState(true);
2063
+ const [error, setError] = useState(null);
2064
+ const router = useRouter();
2065
+ const userProfileTable = authConfig?.userProfileTable;
2066
+ const roleColumn = authConfig?.roleColumn || "role";
2067
+ const fetchUserDetails = useCallback(async (supabaseUser) => {
2068
+ if (!supabaseUser) return null;
2069
+ const basicUser = {
2070
+ id: supabaseUser.id,
2071
+ email: supabaseUser.email
2072
+ };
2073
+ if (!userProfileTable || !supabase) return basicUser;
2074
+ try {
2075
+ const timeoutPromise = new Promise(
2076
+ (_, reject) => setTimeout(() => reject(new Error("Profile fetch timeout")), 5e3)
2077
+ );
2078
+ const fetchPromise = supabase.from(userProfileTable).select(roleColumn).eq("id", supabaseUser.id).single();
2079
+ const { data: profile, error: profileError } = await Promise.race([
2080
+ fetchPromise,
2081
+ timeoutPromise
2082
+ ]);
2083
+ if (profileError) {
2084
+ if (profileError.message.includes("does not exist") || profileError.message.includes("No rows found") || profileError.code === "PGRST116") {
2085
+ console.log("User profile table not found or user not in table, using basic auth info");
2086
+ return basicUser;
2087
+ }
2088
+ console.error("Error fetching user profile:", profileError);
2089
+ return basicUser;
2090
+ }
2091
+ const roleValue = profile ? profile[roleColumn] : void 0;
2092
+ return { ...basicUser, role: roleValue };
2093
+ } catch (err) {
2094
+ console.error("Error fetching user profile:", err);
2095
+ return basicUser;
2096
+ }
2097
+ }, [supabase, userProfileTable, roleColumn]);
2098
+ useEffect(() => {
2099
+ if (!supabase) return;
2100
+ let mounted = true;
2101
+ const safetyTimeout = setTimeout(() => {
2102
+ if (mounted) {
2103
+ console.warn("Auth initialization taking too long, forcing loading to false");
2104
+ setLoading(false);
2105
+ }
2106
+ }, 1e4);
2107
+ const initializeAuth = async () => {
2108
+ try {
2109
+ const { data: { session: initialSession }, error: sessionError } = await supabase.auth.getSession();
2110
+ if (!mounted) return;
2111
+ if (sessionError) {
2112
+ setError(sessionError);
2113
+ setLoading(false);
2114
+ clearTimeout(safetyTimeout);
2115
+ return;
2116
+ }
2117
+ setSession(initialSession);
2118
+ setLoading(false);
2119
+ if (initialSession?.user) {
2120
+ try {
2121
+ const userDetails = await fetchUserDetails(initialSession.user);
2122
+ if (mounted) {
2123
+ setUser(userDetails);
2124
+ if (userDetails) {
2125
+ identifyCoreUser(userDetails.id, {
2126
+ email: userDetails.email,
2127
+ name: userDetails.email,
2128
+ // using email as the display name for now
2129
+ role: userDetails.role
2130
+ });
2131
+ }
2132
+ }
2133
+ } catch (err) {
2134
+ console.error("Error fetching user details during init:", err);
2135
+ if (mounted) {
2136
+ setUser({
2137
+ id: initialSession.user.id,
2138
+ email: initialSession.user.email
2139
+ });
2140
+ }
2141
+ }
2142
+ }
2143
+ } catch (err) {
2144
+ if (mounted) setError(err instanceof Error ? err : new Error("Failed to initialize auth"));
2145
+ } finally {
2146
+ if (mounted) {
2147
+ setLoading(false);
2148
+ clearTimeout(safetyTimeout);
2149
+ }
2150
+ }
2151
+ };
2152
+ initializeAuth();
2153
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(async (_event, currentSession) => {
2154
+ if (!mounted) return;
2155
+ setSession(currentSession);
2156
+ setUser(null);
2157
+ setLoading(false);
2158
+ if (currentSession?.user) {
2159
+ try {
2160
+ const userDetails = await fetchUserDetails(currentSession.user);
2161
+ if (mounted) {
2162
+ setUser(userDetails);
2163
+ if (userDetails) {
2164
+ identifyCoreUser(userDetails.id, {
2165
+ email: userDetails.email,
2166
+ name: userDetails.email,
2167
+ role: userDetails.role
2168
+ });
2169
+ }
2170
+ }
2171
+ } catch (err) {
2172
+ console.error("Error fetching user details on auth state change:", err);
2173
+ if (mounted) {
2174
+ setUser({
2175
+ id: currentSession.user.id,
2176
+ email: currentSession.user.email
2177
+ });
2178
+ }
2179
+ }
2180
+ }
2181
+ if (mounted) setLoading(false);
2182
+ });
2183
+ return () => {
2184
+ mounted = false;
2185
+ clearTimeout(safetyTimeout);
2186
+ subscription?.unsubscribe();
2187
+ };
2188
+ }, [supabase, fetchUserDetails]);
2189
+ const signOut = async () => {
2190
+ if (!supabase) return;
2191
+ setLoading(true);
2192
+ const { error: signOutError } = await supabase.auth.signOut();
2193
+ if (signOutError) setError(signOutError);
2194
+ const logoutRedirectPath = authConfig?.defaultLogoutRedirect || "/login";
2195
+ if (router && router.pathname !== logoutRedirectPath && !router.pathname.startsWith(logoutRedirectPath)) {
2196
+ router.replace(logoutRedirectPath);
2197
+ }
2198
+ };
2199
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value: { session, user, loading, error, signOut }, children });
2200
+ };
2201
+ var defaultContextValue = {
2202
+ components: {},
2203
+ hooks: {},
2204
+ pages: {}
2205
+ };
2206
+ var DashboardOverridesContext = createContext(defaultContextValue);
2207
+ var DashboardOverridesProvider = ({
2208
+ overrides = defaultContextValue,
2209
+ children
2210
+ }) => {
2211
+ const normalizedOverrides = useMemo(() => {
2212
+ return {
2213
+ components: overrides.components || {},
2214
+ hooks: overrides.hooks || {},
2215
+ pages: overrides.pages || {}
2216
+ };
2217
+ }, [overrides]);
2218
+ return /* @__PURE__ */ jsx(DashboardOverridesContext.Provider, { value: normalizedOverrides, children });
2219
+ };
2220
+ function useComponentOverride(key, Default) {
2221
+ const { components = {} } = useContext(DashboardOverridesContext);
2222
+ return components[key] ?? Default;
2223
+ }
2224
+ function useHookOverride(key, Default) {
2225
+ const { hooks = {} } = useContext(DashboardOverridesContext);
2226
+ return hooks[key] ?? Default;
2227
+ }
2228
+ function usePageOverride(key, Default) {
2229
+ const { pages = {} } = useContext(DashboardOverridesContext);
2230
+ return pages[key] ?? Default;
2231
+ }
2232
+ function useOverrides() {
2233
+ return useContext(DashboardOverridesContext);
2234
+ }
2235
+ var SupabaseContext = createContext(void 0);
2236
+ var SupabaseProvider = ({ client, children }) => {
2237
+ _setSupabaseInstance(client);
2238
+ useEffect(() => {
2239
+ _setSupabaseInstance(client);
2240
+ }, [client]);
2241
+ const contextValue = useMemo(() => ({ supabase: client }), [client]);
2242
+ return /* @__PURE__ */ jsx(SupabaseContext.Provider, { value: contextValue, children });
2243
+ };
2244
+ var useSupabase = () => {
2245
+ const context = useContext(SupabaseContext);
2246
+ if (context === void 0) {
2247
+ throw new Error("useSupabase must be used within a SupabaseProvider.");
2248
+ }
2249
+ return context.supabase;
2250
+ };
2251
+ var DEFAULT_COMPANY_ID = "default-company-id";
2252
+ var useWorkspaceMetrics = (workspaceId) => {
2253
+ const supabase = useSupabase();
2254
+ const entityConfig = useEntityConfig();
2255
+ useDatabaseConfig();
2256
+ const dateTimeConfig = useDateTimeConfig();
2257
+ const [workspaceMetrics, setWorkspaceMetrics] = useState(null);
2258
+ const [isLoading, setIsLoading] = useState(true);
2259
+ const [error, setError] = useState(null);
2260
+ const fetchWorkspaceMetrics = useCallback(async () => {
2261
+ try {
2262
+ const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
2263
+ const { data, error: fetchError } = await supabase.from("overview_workspace_metrics").select("*").eq("workspace_id", workspaceId).eq("date", operationalDate).single();
2264
+ if (fetchError) throw fetchError;
2265
+ setWorkspaceMetrics(data);
2266
+ } catch (err) {
2267
+ setError({ message: err.message, code: err.code });
2268
+ console.error("Error fetching workspace metrics:", err);
2269
+ } finally {
2270
+ setIsLoading(false);
2271
+ }
2272
+ }, [supabase, workspaceId, dateTimeConfig.defaultTimezone]);
2273
+ useEffect(() => {
2274
+ let channels = [];
2275
+ const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
2276
+ const setupSubscriptions = () => {
2277
+ const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
2278
+ const metricsTablePrefix = getMetricsTablePrefix();
2279
+ const metricsTable = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
2280
+ const metricsChannel = supabase.channel("workspace-metrics").on(
2281
+ "postgres_changes",
2282
+ {
2283
+ event: "*",
2284
+ schema: "public",
2285
+ table: metricsTable,
2286
+ filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
2287
+ },
2288
+ async (payload) => {
2289
+ console.log(`Received ${metricsTablePrefix} update:`, payload);
2290
+ await fetchWorkspaceMetrics();
2291
+ }
2292
+ ).subscribe((status) => {
2293
+ console.log(`${metricsTablePrefix} subscription status:`, status);
2294
+ });
2295
+ const overviewChannel = supabase.channel("workspace-overview-metrics").on(
2296
+ "postgres_changes",
2297
+ {
2298
+ event: "*",
2299
+ schema: "public",
2300
+ table: "overview_workspace_metrics",
2301
+ filter: `workspace_id=eq.${workspaceId} and date=eq.${operationalDate}`
2302
+ },
2303
+ async (payload) => {
2304
+ console.log("Received overview metrics update:", payload);
2305
+ await fetchWorkspaceMetrics();
2306
+ }
2307
+ ).subscribe((status) => {
2308
+ console.log("Overview metrics subscription status:", status);
2309
+ });
2310
+ channels = [metricsChannel, overviewChannel];
2311
+ };
2312
+ fetchWorkspaceMetrics();
2313
+ setupSubscriptions();
2314
+ return () => {
2315
+ channels.forEach((channel) => {
2316
+ console.log("Cleaning up channel subscription");
2317
+ supabase.removeChannel(channel);
2318
+ });
2319
+ };
2320
+ }, [supabase, workspaceId, fetchWorkspaceMetrics, entityConfig.companyId, dateTimeConfig.defaultTimezone]);
2321
+ return { workspaceMetrics, isLoading, error, refetch: fetchWorkspaceMetrics };
2322
+ };
2323
+ var useLineMetrics = (lineId) => {
2324
+ const supabase = useSupabase();
2325
+ const dateTimeConfig = useDateTimeConfig();
2326
+ const [lineMetrics, setLineMetrics] = useState(null);
2327
+ const [isLoading, setIsLoading] = useState(true);
2328
+ const [error, setError] = useState(null);
2329
+ const fetchLineMetrics = useCallback(async () => {
2330
+ try {
2331
+ const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
2332
+ const { data, error: fetchError } = await supabase.from("overview_line_metrics").select("*").eq("line_id", lineId).eq("date", operationalDate).single();
2333
+ if (fetchError) throw fetchError;
2334
+ setLineMetrics(data);
2335
+ } catch (err) {
2336
+ setError({ message: err.message, code: err.code });
2337
+ console.error("Error fetching line metrics:", err);
2338
+ } finally {
2339
+ setIsLoading(false);
2340
+ }
2341
+ }, [supabase, lineId, dateTimeConfig.defaultTimezone]);
2342
+ useEffect(() => {
2343
+ let channels = [];
2344
+ const operationalDate = getOperationalDate(dateTimeConfig.defaultTimezone);
2345
+ const setupSubscriptions = () => {
2346
+ const lineMetricsChannel = supabase.channel("line-base-metrics").on(
2347
+ "postgres_changes",
2348
+ {
2349
+ event: "*",
2350
+ schema: "public",
2351
+ table: "line_metrics",
2352
+ filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
2353
+ },
2354
+ async (payload) => {
2355
+ console.log("Received line metrics update:", payload);
2356
+ await fetchLineMetrics();
2357
+ }
2358
+ ).subscribe((status) => {
2359
+ console.log("Line metrics subscription status:", status);
2360
+ });
2361
+ const overviewChannel = supabase.channel("line-overview-metrics").on(
2362
+ "postgres_changes",
2363
+ {
2364
+ event: "*",
2365
+ schema: "public",
2366
+ table: "overview_line_metrics",
2367
+ filter: `line_id=eq.${lineId} and date=eq.${operationalDate}`
2368
+ },
2369
+ async (payload) => {
2370
+ console.log("Received line overview update:", payload);
2371
+ await fetchLineMetrics();
2372
+ }
2373
+ ).subscribe((status) => {
2374
+ console.log("Line overview subscription status:", status);
2375
+ });
2376
+ channels = [lineMetricsChannel, overviewChannel];
2377
+ };
2378
+ fetchLineMetrics();
2379
+ setupSubscriptions();
2380
+ return () => {
2381
+ channels.forEach((channel) => {
2382
+ console.log("Cleaning up channel subscription");
2383
+ supabase.removeChannel(channel);
2384
+ });
2385
+ };
2386
+ }, [supabase, lineId, fetchLineMetrics, dateTimeConfig.defaultTimezone]);
2387
+ return { lineMetrics, isLoading, error, refetch: fetchLineMetrics };
2388
+ };
2389
+ var useMetrics = (tableName, options) => {
2390
+ const supabase = useSupabase();
2391
+ const entityConfig = useEntityConfig();
2392
+ const [data, setData] = useState([]);
2393
+ const [isLoading, setIsLoading] = useState(true);
2394
+ const [error, setError] = useState(null);
2395
+ const channelRef = useRef(null);
2396
+ useEffect(() => {
2397
+ const fetchData = async () => {
2398
+ try {
2399
+ setIsLoading(true);
2400
+ setError(null);
2401
+ let actualTableName = tableName;
2402
+ if (tableName === "metrics") {
2403
+ const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
2404
+ const metricsTablePrefix = getMetricsTablePrefix(companyId);
2405
+ actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
2406
+ }
2407
+ let query = supabase.from(actualTableName).select("*");
2408
+ if (options?.filter) {
2409
+ Object.entries(options.filter).forEach(([key, value]) => {
2410
+ query = query.eq(key, value);
2411
+ });
2412
+ }
2413
+ const { data: result, error: fetchError } = await query;
2414
+ if (fetchError) throw fetchError;
2415
+ setData(result);
2416
+ } catch (err) {
2417
+ console.error(`Error fetching data from ${tableName}:`, err);
2418
+ setError({ message: err.message, code: err.code || "FETCH_ERROR" });
2419
+ } finally {
2420
+ setIsLoading(false);
2421
+ }
2422
+ };
2423
+ const setupSubscription = () => {
2424
+ if (!options?.realtime) return;
2425
+ let actualTableName = tableName;
2426
+ if (tableName === "metrics") {
2427
+ const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
2428
+ const metricsTablePrefix = getMetricsTablePrefix();
2429
+ actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
2430
+ }
2431
+ const filter2 = {};
2432
+ if (options?.filter) {
2433
+ Object.entries(options.filter).forEach(([key, value]) => {
2434
+ filter2[`${key}=eq.${value}`] = value;
2435
+ });
2436
+ }
2437
+ channelRef.current = supabase.channel(`${tableName}-changes`).on(
2438
+ "postgres_changes",
2439
+ {
2440
+ event: "*",
2441
+ schema: "public",
2442
+ table: actualTableName,
2443
+ filter: Object.keys(filter2).length > 0 ? Object.keys(filter2).join(",") : void 0
2444
+ },
2445
+ () => {
2446
+ fetchData();
2447
+ }
2448
+ ).subscribe();
2449
+ };
2450
+ fetchData();
2451
+ setupSubscription();
2452
+ return () => {
2453
+ if (channelRef.current) {
2454
+ supabase.removeChannel(channelRef.current);
2455
+ }
2456
+ };
2457
+ }, [supabase, tableName, options, entityConfig.companyId]);
2458
+ const refetch = async () => {
2459
+ setIsLoading(true);
2460
+ try {
2461
+ let actualTableName = tableName;
2462
+ if (tableName === "metrics") {
2463
+ const companyId = entityConfig.companyId || DEFAULT_COMPANY_ID;
2464
+ const metricsTablePrefix = getMetricsTablePrefix(companyId);
2465
+ actualTableName = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
2466
+ }
2467
+ let query = supabase.from(actualTableName).select("*");
2468
+ if (options?.filter) {
2469
+ Object.entries(options.filter).forEach(([key, value]) => {
2470
+ query = query.eq(key, value);
2471
+ });
2472
+ }
2473
+ const { data: result, error: fetchError } = await query;
2474
+ if (fetchError) throw fetchError;
2475
+ setData(result);
2476
+ setError(null);
2477
+ } catch (err) {
2478
+ console.error(`Error refetching data from ${tableName}:`, err);
2479
+ setError({ message: err.message, code: err.code || "FETCH_ERROR" });
2480
+ } finally {
2481
+ setIsLoading(false);
2482
+ }
2483
+ };
2484
+ return { data, isLoading, error, refetch };
2485
+ };
2486
+ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId) => {
2487
+ const entityConfig = useEntityConfig();
2488
+ const databaseConfig = useDatabaseConfig();
2489
+ const dateTimeConfig = useDateTimeConfig();
2490
+ const shiftConfig = useShiftConfig();
2491
+ const workspaceConfig = useWorkspaceConfig();
2492
+ const supabase = useSupabase();
2493
+ const [metrics2, setMetrics] = useState(null);
2494
+ const [isLoading, setIsLoading] = useState(true);
2495
+ const [error, setError] = useState(null);
2496
+ const updateQueueRef = useRef(false);
2497
+ const isFetchingRef = useRef(false);
2498
+ const timeoutRef = useRef(null);
2499
+ const channelRef = useRef(null);
2500
+ const schema = databaseConfig.schema ?? "public";
2501
+ const companyId = entityConfig.companyId || "";
2502
+ const metricsTablePrefix = getMetricsTablePrefix();
2503
+ const metricsTable = `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
2504
+ const defaultTimezone = dateTimeConfig.defaultTimezone;
2505
+ const workspaceMetricsBaseTable = databaseConfig.tables?.workspaces ?? "workspace_metrics";
2506
+ const workspaceActionsTable = databaseConfig.tables?.actions ?? "workspace_actions";
2507
+ const fetchMetrics = useCallback(async () => {
2508
+ if (!workspaceId || isFetchingRef.current) return;
2509
+ try {
2510
+ isFetchingRef.current = true;
2511
+ const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
2512
+ const queryDate = date || currentShift.date;
2513
+ const queryShiftId = shiftId !== void 0 ? shiftId : currentShift.shiftId;
2514
+ console.log("[useWorkspaceDetailedMetrics] Using shift ID:", queryShiftId, "from input shift:", shiftId);
2515
+ console.log("[useWorkspaceDetailedMetrics] Using date:", queryDate, "from input date:", date);
2516
+ console.log(`[useWorkspaceDetailedMetrics] Querying ${metricsTable} for workspace: ${workspaceId}, date: ${queryDate}, shift: ${queryShiftId}`);
2517
+ const { data, error: fetchError } = await supabase.from(metricsTable).select("*").eq("workspace_id", workspaceId).eq("date", queryDate).eq("shift_id", queryShiftId).maybeSingle();
2518
+ if (fetchError) throw fetchError;
2519
+ if (!data && !date && shiftId === void 0) {
2520
+ console.log("[useWorkspaceDetailedMetrics] No data found for current date/shift, attempting to find most recent data...");
2521
+ 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();
2522
+ if (recentError) throw recentError;
2523
+ if (recentData) {
2524
+ console.log(`[useWorkspaceDetailedMetrics] Found fallback data from date: ${recentData.date}, shift: ${recentData.shift_id}`);
2525
+ const outputDifference2 = (recentData.total_output || 0) - (recentData.ideal_output || 0);
2526
+ const workspaceMatch2 = recentData.workspace_name?.match(/WS(\d+)/);
2527
+ const workspaceNumber2 = workspaceMatch2 ? parseInt(workspaceMatch2[1]) : 0;
2528
+ const specialWsStart2 = workspaceConfig.specialWorkspaces?.startId ?? 19;
2529
+ const specialWsEnd2 = workspaceConfig.specialWorkspaces?.endId ?? 34;
2530
+ const isSpecialWorkspace2 = workspaceNumber2 >= specialWsStart2 && workspaceNumber2 <= specialWsEnd2;
2531
+ const outputHourly2 = recentData.output_hourly || {};
2532
+ const hasOutputHourlyData2 = outputHourly2 && typeof outputHourly2 === "object" && Object.keys(outputHourly2).length > 0;
2533
+ let hourlyActionCounts2 = [];
2534
+ if (hasOutputHourlyData2) {
2535
+ console.log("Using new output_hourly column for workspace (fallback):", recentData.workspace_name);
2536
+ console.log("Raw output_hourly data (fallback):", outputHourly2);
2537
+ const isNightShift = recentData.shift_id === 1;
2538
+ const shiftStart = recentData.shift_start || (isNightShift ? "22:00" : "06:00");
2539
+ const shiftEnd = recentData.shift_end || (isNightShift ? "06:00" : "14:00");
2540
+ const startHour = parseInt(shiftStart.split(":")[0]);
2541
+ let expectedHours = [];
2542
+ if (isNightShift) {
2543
+ for (let i = 0; i < 9; i++) {
2544
+ expectedHours.push((startHour + i) % 24);
2545
+ }
2546
+ } else {
2547
+ for (let i = 0; i < 9; i++) {
2548
+ expectedHours.push((startHour + i) % 24);
2549
+ }
2550
+ }
2551
+ console.log("Expected shift hours (fallback):", expectedHours);
2552
+ console.log("Available data hours (fallback):", Object.keys(outputHourly2));
2553
+ hourlyActionCounts2 = expectedHours.map((expectedHour) => {
2554
+ let hourData = outputHourly2[expectedHour.toString()];
2555
+ if (!hourData && isNightShift) {
2556
+ for (const [storedHour, data2] of Object.entries(outputHourly2)) {
2557
+ if (Array.isArray(data2) && data2.length > 0 && data2.some((val) => val > 0)) {
2558
+ if (storedHour === "18" && expectedHour === 1) {
2559
+ hourData = data2;
2560
+ console.log(`Mapping stored hour ${storedHour} to expected hour ${expectedHour} (fallback)`);
2561
+ break;
2562
+ }
2563
+ }
2564
+ }
2565
+ }
2566
+ return Array.isArray(hourData) ? hourData.reduce((sum, count) => sum + (count || 0), 0) : 0;
2567
+ });
2568
+ console.log("Final hourly action counts (fallback):", hourlyActionCounts2);
2569
+ } else {
2570
+ console.log("Using output_array fallback for workspace (fallback):", recentData.workspace_name);
2571
+ const minuteByMinuteArray = recentData.output_array || [];
2572
+ if (isSpecialWorkspace2) {
2573
+ const last40Readings = minuteByMinuteArray.slice(Math.max(0, minuteByMinuteArray.length - 40));
2574
+ hourlyActionCounts2 = last40Readings;
2575
+ } else {
2576
+ for (let i = 0; i < minuteByMinuteArray.length; i += 60) {
2577
+ const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
2578
+ const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
2579
+ hourlyActionCounts2.push(hourlySum);
2580
+ }
2581
+ }
2582
+ }
2583
+ const transformedData2 = {
2584
+ workspace_id: recentData.workspace_id,
2585
+ workspace_name: recentData.workspace_name,
2586
+ line_id: recentData.line_id,
2587
+ line_name: recentData.line_name || "Line 1",
2588
+ company_id: recentData.company_id || companyId,
2589
+ company_name: recentData.company_name || "Nahar Group",
2590
+ date: recentData.date,
2591
+ shift_id: recentData.shift_id,
2592
+ action_name: recentData.action_name || "",
2593
+ shift_start: recentData.shift_start || "06:00",
2594
+ shift_end: recentData.shift_end || "14:00",
2595
+ shift_type: recentData.shift_type || (recentData.shift_id === 0 ? "Day" : "Night"),
2596
+ pph_threshold: recentData.pph_threshold || 0,
2597
+ target_output: recentData.total_day_output || 0,
2598
+ avg_pph: recentData.avg_pph || 0,
2599
+ avg_cycle_time: recentData.avg_cycle_time || 0,
2600
+ ideal_cycle_time: recentData.ideal_cycle_time || 0,
2601
+ avg_efficiency: recentData.efficiency || 0,
2602
+ total_actions: recentData.total_output || 0,
2603
+ hourly_action_counts: hourlyActionCounts2,
2604
+ // Now uses the NEW logic with fallback
2605
+ workspace_rank: recentData.workspace_rank || 0,
2606
+ total_workspaces: recentData.total_workspaces || workspaceConfig.totalWorkspaces || 42,
2607
+ ideal_output_until_now: recentData.ideal_output || 0,
2608
+ output_difference: outputDifference2,
2609
+ idle_time: recentData.idle_time || 0,
2610
+ idle_time_hourly: recentData.idle_time_hourly || void 0,
2611
+ ...recentData.compliance_efficiency !== void 0 && { compliance_efficiency: recentData.compliance_efficiency },
2612
+ ...recentData.sop_check !== void 0 && { sop_check: recentData.sop_check }
2613
+ };
2614
+ setMetrics(transformedData2);
2615
+ setIsLoading(false);
2616
+ updateQueueRef.current = false;
2617
+ isFetchingRef.current = false;
2618
+ return;
2619
+ } else {
2620
+ console.warn("[useWorkspaceDetailedMetrics] No data found for workspace:", workspaceId, "at all");
2621
+ }
2622
+ }
2623
+ if (!data) {
2624
+ console.warn("[useWorkspaceDetailedMetrics] No detailed metrics found for workspace:", workspaceId);
2625
+ setMetrics(null);
2626
+ setIsLoading(false);
2627
+ updateQueueRef.current = false;
2628
+ isFetchingRef.current = false;
2629
+ return;
2630
+ }
2631
+ const outputDifference = (data.total_output || 0) - (data.ideal_output || 0);
2632
+ const workspaceMatch = data.workspace_name?.match(/WS(\d+)/);
2633
+ const workspaceNumber = workspaceMatch ? parseInt(workspaceMatch[1]) : 0;
2634
+ const specialWsStart = workspaceConfig.specialWorkspaces?.startId ?? 19;
2635
+ const specialWsEnd = workspaceConfig.specialWorkspaces?.endId ?? 34;
2636
+ const isSpecialWorkspace = workspaceNumber >= specialWsStart && workspaceNumber <= specialWsEnd;
2637
+ const outputHourly = data.output_hourly || {};
2638
+ console.log("[DEBUG] Raw data.output_hourly:", data.output_hourly);
2639
+ console.log("[DEBUG] outputHourly after || {}:", outputHourly);
2640
+ console.log("[DEBUG] typeof outputHourly:", typeof outputHourly);
2641
+ console.log("[DEBUG] Object.keys(outputHourly):", Object.keys(outputHourly));
2642
+ console.log("[DEBUG] Object.keys(outputHourly).length:", Object.keys(outputHourly).length);
2643
+ const hasOutputHourlyData = outputHourly && typeof outputHourly === "object" && Object.keys(outputHourly).length > 0;
2644
+ console.log("[DEBUG] hasOutputHourlyData:", hasOutputHourlyData);
2645
+ let hourlyActionCounts = [];
2646
+ if (hasOutputHourlyData) {
2647
+ console.log("\u2705 Using new output_hourly column for workspace:", data.workspace_name);
2648
+ console.log("Raw output_hourly data:", JSON.stringify(outputHourly));
2649
+ const isNightShift = data.shift_id === 1;
2650
+ const shiftStart = data.shift_start || (isNightShift ? "22:00" : "06:00");
2651
+ const shiftEnd = data.shift_end || (isNightShift ? "06:00" : "14:00");
2652
+ const startHour = parseInt(shiftStart.split(":")[0]);
2653
+ let expectedHours = [];
2654
+ if (isNightShift) {
2655
+ for (let i = 0; i < 9; i++) {
2656
+ expectedHours.push((startHour + i) % 24);
2657
+ }
2658
+ } else {
2659
+ for (let i = 0; i < 9; i++) {
2660
+ expectedHours.push((startHour + i) % 24);
2661
+ }
2662
+ }
2663
+ console.log("Expected shift hours:", expectedHours);
2664
+ console.log("Available data hours:", Object.keys(outputHourly));
2665
+ hourlyActionCounts = expectedHours.map((expectedHour) => {
2666
+ let hourData = outputHourly[expectedHour.toString()];
2667
+ if (!hourData && isNightShift) {
2668
+ for (const [storedHour, data2] of Object.entries(outputHourly)) {
2669
+ if (Array.isArray(data2) && data2.length > 0 && data2.some((val) => val > 0)) {
2670
+ if (storedHour === "18" && expectedHour === 1) {
2671
+ hourData = data2;
2672
+ console.log(`Mapping stored hour ${storedHour} to expected hour ${expectedHour}`);
2673
+ break;
2674
+ }
2675
+ }
2676
+ }
2677
+ }
2678
+ return Array.isArray(hourData) ? hourData.reduce((sum, count) => sum + (count || 0), 0) : 0;
2679
+ });
2680
+ console.log("Final hourly action counts:", hourlyActionCounts);
2681
+ } else {
2682
+ console.log("\u274C Using output_array fallback for workspace:", data.workspace_name);
2683
+ console.log("[DEBUG] Fallback reason - hasOutputHourlyData is false");
2684
+ console.log("[DEBUG] data.output_hourly was:", data.output_hourly);
2685
+ const minuteByMinuteArray = data.output_array || [];
2686
+ if (isSpecialWorkspace) {
2687
+ const last40Readings = minuteByMinuteArray.slice(Math.max(0, minuteByMinuteArray.length - 40));
2688
+ hourlyActionCounts = last40Readings;
2689
+ } else {
2690
+ for (let i = 0; i < minuteByMinuteArray.length; i += 60) {
2691
+ const hourSlice = minuteByMinuteArray.slice(i, Math.min(i + 60, minuteByMinuteArray.length));
2692
+ const hourlySum = hourSlice.reduce((sum, count) => sum + count, 0);
2693
+ hourlyActionCounts.push(hourlySum);
2694
+ }
2695
+ }
2696
+ console.log("Final hourly action counts:", hourlyActionCounts);
2697
+ }
2698
+ const transformedData = {
2699
+ workspace_id: data.workspace_id,
2700
+ workspace_name: data.workspace_name,
2701
+ line_id: data.line_id,
2702
+ line_name: data.line_name || "Line 1",
2703
+ company_id: data.company_id || companyId,
2704
+ company_name: data.company_name || "Nahar Group",
2705
+ date: data.date,
2706
+ shift_id: data.shift_id,
2707
+ action_name: data.action_name || "",
2708
+ shift_start: data.shift_start || "06:00",
2709
+ shift_end: data.shift_end || "14:00",
2710
+ shift_type: data.shift_type || (data.shift_id === 0 ? "Day" : "Night"),
2711
+ pph_threshold: data.pph_threshold || 0,
2712
+ target_output: data.total_day_output || 0,
2713
+ avg_pph: data.avg_pph || 0,
2714
+ avg_cycle_time: data.avg_cycle_time || 0,
2715
+ ideal_cycle_time: data.ideal_cycle_time || 0,
2716
+ avg_efficiency: data.efficiency || 0,
2717
+ total_actions: data.total_output || 0,
2718
+ hourly_action_counts: hourlyActionCounts,
2719
+ workspace_rank: data.workspace_rank || 0,
2720
+ total_workspaces: data.total_workspaces || workspaceConfig.totalWorkspaces || 42,
2721
+ ideal_output_until_now: data.ideal_output || 0,
2722
+ output_difference: outputDifference,
2723
+ idle_time: data.idle_time || 0,
2724
+ // Add idle_time from performance_metrics table
2725
+ idle_time_hourly: data.idle_time_hourly || void 0,
2726
+ // Add idle_time_hourly from performance_metrics table
2727
+ ...data.compliance_efficiency !== void 0 && { compliance_efficiency: data.compliance_efficiency },
2728
+ ...data.sop_check !== void 0 && { sop_check: data.sop_check }
2729
+ };
2730
+ setMetrics(transformedData);
2731
+ } catch (err) {
2732
+ console.error("Error fetching workspace metrics:", err);
2733
+ setError({ message: err.message, code: err.code });
2734
+ } finally {
2735
+ isFetchingRef.current = false;
2736
+ updateQueueRef.current = false;
2737
+ setIsLoading(false);
2738
+ }
2739
+ }, [supabase, workspaceId, date, shiftId, metricsTable, defaultTimezone, shiftConfig, workspaceConfig, companyId]);
2740
+ const queueUpdate = useCallback(() => {
2741
+ if (!workspaceId || updateQueueRef.current) return;
2742
+ updateQueueRef.current = true;
2743
+ if (timeoutRef.current) {
2744
+ clearTimeout(timeoutRef.current);
2745
+ }
2746
+ timeoutRef.current = setTimeout(() => {
2747
+ fetchMetrics();
2748
+ }, 500);
2749
+ }, [fetchMetrics, workspaceId]);
2750
+ const setupSubscription = useCallback(() => {
2751
+ if (!workspaceId) return;
2752
+ if (channelRef.current) {
2753
+ supabase.removeChannel(channelRef.current);
2754
+ }
2755
+ channelRef.current = supabase.channel("workspace-detailed-metrics").on(
2756
+ "postgres_changes",
2757
+ {
2758
+ event: "*",
2759
+ schema,
2760
+ table: metricsTable,
2761
+ filter: `workspace_id=eq.${workspaceId}`
2762
+ },
2763
+ async (payload) => {
2764
+ console.log(`Received ${metricsTablePrefix} update:`, payload);
2765
+ await fetchMetrics();
2766
+ }
2767
+ ).subscribe((status) => {
2768
+ console.log(`Workspace detailed metrics subscription status:`, status);
2769
+ });
2770
+ }, [supabase, workspaceId, fetchMetrics, schema, metricsTable, metricsTablePrefix]);
2771
+ useEffect(() => {
2772
+ if (!workspaceId) {
2773
+ setIsLoading(false);
2774
+ return;
2775
+ }
2776
+ const channels = [];
2777
+ const operationalDate = date || getOperationalDate();
2778
+ const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
2779
+ const queryShiftId = shiftId ?? currentShift.shiftId;
2780
+ const metricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
2781
+ "postgres_changes",
2782
+ {
2783
+ event: "*",
2784
+ schema,
2785
+ table: metricsTable,
2786
+ filter: `workspace_id=eq.${workspaceId}`
2787
+ },
2788
+ async (payload) => {
2789
+ const payloadData = payload.new;
2790
+ console.log(`Received ${metricsTablePrefix} update:`, {
2791
+ payload,
2792
+ payloadDate: payloadData?.date,
2793
+ payloadShift: payloadData?.shift_id,
2794
+ currentDate: operationalDate,
2795
+ currentShift: queryShiftId,
2796
+ matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
2797
+ });
2798
+ if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
2799
+ queueUpdate();
2800
+ }
2801
+ }
2802
+ ).subscribe((status) => {
2803
+ console.log(`${metricsTablePrefix} subscription status:`, status);
2804
+ });
2805
+ const workspaceMetricsChannel = supabase.channel(`workspace-metrics-${workspaceId}`).on(
2806
+ "postgres_changes",
2807
+ {
2808
+ event: "*",
2809
+ schema,
2810
+ table: workspaceMetricsBaseTable,
2811
+ filter: `workspace_id=eq.${workspaceId}`
2812
+ },
2813
+ async (payload) => {
2814
+ const payloadData = payload.new;
2815
+ console.log("Received workspace_metrics update:", {
2816
+ payload,
2817
+ payloadDate: payloadData?.date,
2818
+ payloadShift: payloadData?.shift_id,
2819
+ currentDate: operationalDate,
2820
+ currentShift: queryShiftId,
2821
+ matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
2822
+ });
2823
+ if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
2824
+ queueUpdate();
2825
+ }
2826
+ }
2827
+ ).subscribe((status) => {
2828
+ console.log(`Workspace metrics subscription status:`, status);
2829
+ });
2830
+ const workspaceActionsChannel = supabase.channel(`workspace-actions-${workspaceId}`).on(
2831
+ "postgres_changes",
2832
+ {
2833
+ event: "*",
2834
+ schema,
2835
+ table: workspaceActionsTable,
2836
+ filter: `workspace_id=eq.${workspaceId}`
2837
+ },
2838
+ async (payload) => {
2839
+ const payloadData = payload.new;
2840
+ console.log("Received workspace_actions update:", {
2841
+ payload,
2842
+ payloadDate: payloadData?.date,
2843
+ payloadShift: payloadData?.shift_id,
2844
+ currentDate: operationalDate,
2845
+ currentShift: queryShiftId,
2846
+ matches: payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId
2847
+ });
2848
+ if (payloadData?.date === operationalDate && payloadData?.shift_id === queryShiftId) {
2849
+ queueUpdate();
2850
+ }
2851
+ }
2852
+ ).subscribe((status) => {
2853
+ console.log(`Workspace actions subscription status:`, status);
2854
+ });
2855
+ channels.push(metricsChannel, workspaceMetricsChannel, workspaceActionsChannel);
2856
+ fetchMetrics();
2857
+ setupSubscription();
2858
+ return () => {
2859
+ if (timeoutRef.current) {
2860
+ clearTimeout(timeoutRef.current);
2861
+ }
2862
+ channels.forEach((channel) => {
2863
+ console.log("Cleaning up channel subscription");
2864
+ supabase.removeChannel(channel);
2865
+ });
2866
+ if (channelRef.current) {
2867
+ supabase.removeChannel(channelRef.current);
2868
+ }
2869
+ };
2870
+ }, [supabase, workspaceId, date, shiftId, fetchMetrics, queueUpdate, setupSubscription, metricsTable, workspaceMetricsBaseTable, workspaceActionsTable, defaultTimezone, shiftConfig, schema, metricsTablePrefix]);
2871
+ return {
2872
+ metrics: metrics2,
2873
+ isLoading,
2874
+ error,
2875
+ refetch: fetchMetrics
2876
+ };
2877
+ };
2878
+ var useLineWorkspaceMetrics = (lineId, options) => {
2879
+ const entityConfig = useEntityConfig();
2880
+ const databaseConfig = useDatabaseConfig();
2881
+ const dateTimeConfig = useDateTimeConfig();
2882
+ const shiftConfig = useShiftConfig();
2883
+ const supabase = useSupabase();
2884
+ const [workspaces, setWorkspaces] = useState([]);
2885
+ const [loading, setLoading] = useState(true);
2886
+ const [error, setError] = useState(null);
2887
+ const [initialized, setInitialized] = useState(false);
2888
+ const queryShiftId = useMemo(() => {
2889
+ const currentShift = getCurrentShift(
2890
+ dateTimeConfig.defaultTimezone || "Asia/Kolkata",
2891
+ shiftConfig
2892
+ );
2893
+ return options?.initialShiftId !== void 0 ? options.initialShiftId : currentShift.shiftId;
2894
+ }, [options?.initialShiftId, dateTimeConfig.defaultTimezone, shiftConfig]);
2895
+ const queryDate = useMemo(() => {
2896
+ return options?.initialDate || getOperationalDate(dateTimeConfig.defaultTimezone);
2897
+ }, [options?.initialDate, dateTimeConfig.defaultTimezone]);
2898
+ const metricsTable = useMemo(() => {
2899
+ const companyId = entityConfig.companyId;
2900
+ if (!companyId) return "";
2901
+ const metricsTablePrefix = getMetricsTablePrefix();
2902
+ return `${metricsTablePrefix}_${companyId.replace(/-/g, "_")}`;
2903
+ }, [entityConfig.companyId]);
2904
+ const schema = databaseConfig.schema ?? "public";
2905
+ const fetchWorkspaceMetrics = useCallback(async () => {
2906
+ if (!lineId) return;
2907
+ if (!initialized) {
2908
+ setLoading(true);
2909
+ }
2910
+ setError(null);
2911
+ try {
2912
+ console.log("Fetching workspace metrics with params:", {
2913
+ lineId,
2914
+ queryDate,
2915
+ queryShiftId,
2916
+ metricsTable
2917
+ });
2918
+ const { data, error: fetchError } = await supabase.from(metricsTable).select(`
2919
+ workspace_name,
2920
+ total_output,
2921
+ avg_pph,
2922
+ efficiency,
2923
+ workspace_id,
2924
+ avg_cycle_time,
2925
+ performance_score,
2926
+ trend_score,
2927
+ line_id,
2928
+ total_day_output
2929
+ `).eq("date", queryDate).eq("shift_id", queryShiftId).eq("line_id", lineId).order("workspace_name", { ascending: true });
2930
+ if (fetchError) throw fetchError;
2931
+ const transformedData = (data || []).map((item) => ({
2932
+ company_id: entityConfig.companyId || "unknown",
2933
+ line_id: item.line_id,
2934
+ shift_id: queryShiftId,
2935
+ date: queryDate,
2936
+ workspace_uuid: item.workspace_id,
2937
+ workspace_name: item.workspace_name,
2938
+ action_count: item.total_output || 0,
2939
+ pph: item.avg_pph || 0,
2940
+ performance_score: item.performance_score || 0,
2941
+ avg_cycle_time: item.avg_cycle_time || 0,
2942
+ trend: item.trend_score === 1 ? 2 : 0,
2943
+ predicted_output: 0,
2944
+ efficiency: item.efficiency || 0,
2945
+ action_threshold: item.total_day_output || 0
2946
+ }));
2947
+ setWorkspaces(transformedData);
2948
+ setInitialized(true);
2949
+ } catch (err) {
2950
+ console.error("Error fetching workspace metrics:", err);
2951
+ setError({ message: err.message, code: err.code || "FETCH_ERROR" });
2952
+ } finally {
2953
+ setLoading(false);
2954
+ }
2955
+ }, [lineId, queryDate, queryShiftId, metricsTable, supabase, entityConfig.companyId]);
2956
+ useEffect(() => {
2957
+ if (!initialized) {
2958
+ fetchWorkspaceMetrics();
2959
+ }
2960
+ const setupSubscription = () => {
2961
+ if (!lineId) return null;
2962
+ const filter2 = `line_id=eq.${lineId} AND date=eq.${queryDate} AND shift_id=eq.${queryShiftId}`;
2963
+ console.log("Setting up subscription with filter:", filter2);
2964
+ const channel2 = supabase.channel(`line-workspace-metrics-${Date.now()}`).on(
2965
+ "postgres_changes",
2966
+ {
2967
+ event: "*",
2968
+ schema,
2969
+ table: metricsTable,
2970
+ filter: filter2
2971
+ },
2972
+ async (payload) => {
2973
+ console.log("Workspace metrics update received:", payload);
2974
+ await fetchWorkspaceMetrics();
2975
+ }
2976
+ ).subscribe();
2977
+ return channel2;
2978
+ };
2979
+ const channel = setupSubscription();
2980
+ return () => {
2981
+ if (channel) {
2982
+ supabase.removeChannel(channel);
2983
+ }
2984
+ };
2985
+ }, [lineId, queryDate, queryShiftId, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema]);
2986
+ useEffect(() => {
2987
+ setInitialized(false);
2988
+ }, [lineId, queryDate, queryShiftId]);
2989
+ const refreshWorkspaces = fetchWorkspaceMetrics;
2990
+ return useMemo(
2991
+ () => ({ workspaces, loading, error, refreshWorkspaces }),
2992
+ [workspaces, loading, error, refreshWorkspaces]
2993
+ );
2994
+ };
2995
+ var useHistoricWorkspaceMetrics = (workspaceId, date, inputShiftId) => {
2996
+ useSupabase();
2997
+ const [metrics2, setMetrics] = useState(null);
2998
+ const [isLoading, setIsLoading] = useState(true);
2999
+ const [error, setError] = useState(null);
3000
+ const fetchAndAnimateMetrics = useCallback(async () => {
3001
+ if (!workspaceId || !date || inputShiftId === void 0) {
3002
+ setMetrics(null);
3003
+ setIsLoading(false);
3004
+ setError(null);
3005
+ return;
2933
3006
  }
3007
+ setIsLoading(true);
3008
+ setError(null);
2934
3009
  try {
2935
- await this.handleSSEStream(response, callbacks);
2936
- } finally {
2937
- this.controllers.delete(connectionId);
3010
+ const fetchedData = await dashboardService.getWorkspaceDetailedMetrics(
3011
+ workspaceId,
3012
+ date,
3013
+ inputShiftId
3014
+ );
3015
+ if (!fetchedData) {
3016
+ console.warn("No historic data found for workspace:", workspaceId, date, inputShiftId);
3017
+ setMetrics(null);
3018
+ setIsLoading(false);
3019
+ return;
3020
+ }
3021
+ const initialHourlyCounts = fetchedData.hourly_action_counts && Array.isArray(fetchedData.hourly_action_counts) ? new Array(fetchedData.hourly_action_counts.length).fill(0) : [];
3022
+ setMetrics({
3023
+ ...fetchedData,
3024
+ hourly_action_counts: initialHourlyCounts
3025
+ });
3026
+ setIsLoading(false);
3027
+ if (fetchedData.hourly_action_counts && fetchedData.hourly_action_counts.length > 0) {
3028
+ const totalSteps = 60;
3029
+ let currentStep = 0;
3030
+ let animationIntervalId = setInterval(() => {
3031
+ currentStep++;
3032
+ const progress6 = currentStep / totalSteps;
3033
+ setMetrics((prevMetrics) => {
3034
+ const currentHourlyData = (fetchedData.hourly_action_counts || []).map(
3035
+ (target) => Math.round(target * progress6)
3036
+ );
3037
+ return {
3038
+ ...fetchedData,
3039
+ // Base with all other correct data from the latest fetch
3040
+ hourly_action_counts: currentHourlyData
3041
+ };
3042
+ });
3043
+ if (currentStep >= totalSteps) {
3044
+ if (animationIntervalId) clearInterval(animationIntervalId);
3045
+ setMetrics(fetchedData);
3046
+ }
3047
+ }, 1e3 / 60);
3048
+ } else {
3049
+ setMetrics(fetchedData);
3050
+ }
3051
+ } catch (err) {
3052
+ console.error("Error fetching historic workspace metrics:", err);
3053
+ setError({ message: err.message, code: err.code || "FETCH_ERROR" });
3054
+ setIsLoading(false);
3055
+ setMetrics(null);
2938
3056
  }
2939
- }
2940
- async handleSSEStream(response, callbacks) {
2941
- if (!response.body) {
2942
- console.error("[SSEClient] Response body is null");
2943
- throw new Error("No response body available for streaming");
3057
+ }, [workspaceId, date, inputShiftId]);
3058
+ useEffect(() => {
3059
+ fetchAndAnimateMetrics();
3060
+ }, [fetchAndAnimateMetrics]);
3061
+ const refetch = useCallback(async () => {
3062
+ if (!workspaceId || !date || inputShiftId === void 0) {
3063
+ setError(null);
3064
+ return;
2944
3065
  }
2945
- const reader = response.body.getReader();
2946
- const decoder = new TextDecoder();
2947
- let buffer = "";
3066
+ setIsLoading(true);
3067
+ setError(null);
2948
3068
  try {
2949
- console.log("[SSEClient] Starting to read stream...");
2950
- while (true) {
2951
- const { done, value } = await reader.read();
2952
- if (done) {
2953
- console.log("[SSEClient] Stream ended");
2954
- break;
2955
- }
2956
- buffer += decoder.decode(value, { stream: true });
2957
- const lines = buffer.split("\n");
2958
- buffer = lines.pop() || "";
2959
- for (let i = 0; i < lines.length; i++) {
2960
- const line = lines[i].trim();
2961
- if (!line) continue;
2962
- console.log("[SSEClient] Processing line:", line);
2963
- if (line.startsWith("event:")) {
2964
- const event = line.slice(6).trim();
2965
- console.log("[SSEClient] Event type:", event);
2966
- const nextLine = lines[i + 1];
2967
- if (nextLine?.startsWith("data:")) {
2968
- const dataStr = nextLine.slice(5).trim();
2969
- console.log("[SSEClient] Event data:", dataStr);
2970
- try {
2971
- const data = JSON.parse(dataStr);
2972
- switch (event) {
2973
- case "thread":
2974
- callbacks.onThreadCreated?.(data.thread_id);
2975
- break;
2976
- case "message":
2977
- callbacks.onMessage?.(data.text);
2978
- break;
2979
- case "reasoning":
2980
- callbacks.onReasoning?.(data.text);
2981
- break;
2982
- case "complete":
2983
- callbacks.onComplete?.(data.message_id);
2984
- break;
2985
- case "error":
2986
- callbacks.onError?.(data.error);
2987
- break;
2988
- }
2989
- } catch (e) {
2990
- console.error("[SSEClient] Failed to parse data:", dataStr, e);
2991
- }
2992
- i++;
2993
- }
2994
- }
2995
- }
3069
+ const fetchedData = await dashboardService.getWorkspaceDetailedMetrics(
3070
+ workspaceId,
3071
+ date,
3072
+ inputShiftId
3073
+ );
3074
+ if (!fetchedData) {
3075
+ setMetrics(null);
3076
+ return;
2996
3077
  }
3078
+ setMetrics(fetchedData);
3079
+ } catch (err) {
3080
+ console.error("Error re-fetching historic workspace metrics:", err);
3081
+ setError({ message: err.message, code: err.code || "FETCH_ERROR" });
3082
+ setMetrics(null);
2997
3083
  } finally {
2998
- reader.releaseLock();
2999
- }
3000
- }
3001
- abort(threadId) {
3002
- if (threadId) {
3003
- for (const [id3, controller] of this.controllers.entries()) {
3004
- if (id3.startsWith(threadId)) {
3005
- controller.abort();
3006
- this.controllers.delete(id3);
3007
- }
3008
- }
3009
- } else {
3010
- for (const [id3, controller] of this.controllers.entries()) {
3011
- controller.abort();
3012
- }
3013
- this.controllers.clear();
3084
+ setIsLoading(false);
3014
3085
  }
3015
- }
3016
- };
3017
-
3018
- // src/lib/services/chatService.ts
3019
- async function getUserThreads(userId, limit = 20) {
3020
- const supabase = _getSupabaseInstance();
3021
- const { data, error } = await supabase.schema("ai").from("chat_threads").select("*").eq("user_id", userId).order("updated_at", { ascending: false }).limit(limit);
3022
- if (error) throw error;
3023
- return data;
3024
- }
3025
- async function getUserThreadsPaginated(userId, page = 1, pageSize = 20) {
3026
- const supabase = _getSupabaseInstance();
3027
- const from = (page - 1) * pageSize;
3028
- const to = from + pageSize - 1;
3029
- 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);
3030
- if (error) throw error;
3086
+ }, [workspaceId, date, inputShiftId]);
3031
3087
  return {
3032
- threads: data,
3033
- totalCount: count || 0,
3034
- totalPages: Math.ceil((count || 0) / pageSize),
3035
- currentPage: page
3088
+ metrics: metrics2,
3089
+ isLoading,
3090
+ error,
3091
+ refetch
3036
3092
  };
3037
- }
3038
- async function getThreadMessages(threadId, limit = 50, beforePosition) {
3039
- const supabase = _getSupabaseInstance();
3040
- let query = supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
3041
- if (beforePosition !== void 0) {
3042
- query = query.lt("position", beforePosition);
3043
- }
3044
- const { data, error } = await query.limit(limit);
3045
- if (error) throw error;
3046
- return data;
3047
- }
3048
- async function getAllThreadMessages(threadId) {
3049
- const supabase = _getSupabaseInstance();
3050
- const { data, error } = await supabase.schema("ai").from("chat_messages").select("*").eq("thread_id", threadId).order("position", { ascending: true });
3051
- if (error) throw error;
3052
- return data;
3053
- }
3054
- async function updateThreadTitle(threadId, newTitle) {
3055
- const supabase = _getSupabaseInstance();
3056
- const { data, error } = await supabase.schema("ai").from("chat_threads").update({
3057
- title: newTitle,
3058
- auto_title: false,
3059
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
3060
- }).eq("id", threadId).select().single();
3061
- if (error) throw error;
3062
- return data;
3063
- }
3064
- async function deleteThread(threadId) {
3065
- const supabase = _getSupabaseInstance();
3066
- const { error } = await supabase.schema("ai").from("chat_threads").delete().eq("id", threadId);
3067
- if (error) throw error;
3068
- }
3069
-
3070
- // src/lib/hooks/useLineDetailedMetrics.ts
3093
+ };
3071
3094
  var useLineDetailedMetrics = (lineIdFromProp) => {
3072
3095
  const entityConfig = useEntityConfig();
3073
3096
  const databaseConfig = useDatabaseConfig();
@@ -19634,7 +19657,8 @@ var WorkspaceHistoryCalendar = ({
19634
19657
  pph: 0,
19635
19658
  pphThreshold: 0,
19636
19659
  idealOutput: 0,
19637
- rank: 0
19660
+ rank: 0,
19661
+ idleTime: 0
19638
19662
  },
19639
19663
  nightShift: {
19640
19664
  efficiency: 0,
@@ -19643,7 +19667,8 @@ var WorkspaceHistoryCalendar = ({
19643
19667
  pph: 0,
19644
19668
  pphThreshold: 0,
19645
19669
  idealOutput: 0,
19646
- rank: 0
19670
+ rank: 0,
19671
+ idleTime: 0
19647
19672
  }
19648
19673
  });
19649
19674
  }
@@ -19672,7 +19697,8 @@ var WorkspaceHistoryCalendar = ({
19672
19697
  avgEfficiency: Math.round(validShifts.reduce((sum, shift) => sum + shift.efficiency, 0) / validShifts.length),
19673
19698
  avgCycleTime: Math.round(validShifts.reduce((sum, shift) => sum + shift.cycleTime, 0) / validShifts.length),
19674
19699
  badDaysCount: badShiftsCount,
19675
- totalDays: validShifts.length
19700
+ totalDays: validShifts.length,
19701
+ avgIdleTime: Math.round(validShifts.reduce((sum, shift) => sum + (shift.idleTime || 0), 0) / validShifts.length)
19676
19702
  };
19677
19703
  }, [data, month, year, configuredTimezone]);
19678
19704
  const handleDayClick = useCallback((day, shift) => {
@@ -19843,6 +19869,10 @@ var WorkspaceHistoryCalendar = ({
19843
19869
  monthlyMetrics.avgCycleTime,
19844
19870
  "s"
19845
19871
  ] })
19872
+ ] }),
19873
+ /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
19874
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-gray-600 mb-1", children: "Avg Idle Time" }),
19875
+ /* @__PURE__ */ jsx("div", { className: "text-xl font-semibold text-gray-900", children: formatIdleTime(monthlyMetrics.avgIdleTime) })
19846
19876
  ] })
19847
19877
  ] }) : /* @__PURE__ */ jsxs("div", { className: "text-center py-8 text-gray-500 bg-gray-50 rounded-lg border border-gray-200", children: [
19848
19878
  /* @__PURE__ */ jsx("svg", { className: "w-12 h-12 text-gray-400 mx-auto mb-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" }) }),
@@ -29960,8 +29990,8 @@ var WorkspaceDetailView = ({
29960
29990
  if (!dayEntry) {
29961
29991
  dayEntry = {
29962
29992
  date: dateObj,
29963
- dayShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0 },
29964
- nightShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0 }
29993
+ dayShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0, idleTime: 0 },
29994
+ nightShift: { efficiency: 0, output: 0, cycleTime: 0, pph: 0, pphThreshold: 0, idealOutput: 0, rank: 0, idleTime: 0 }
29965
29995
  };
29966
29996
  dayDataMap.set(dateKey, dayEntry);
29967
29997
  }
@@ -29973,6 +30003,7 @@ var WorkspaceDetailView = ({
29973
30003
  shiftTarget.pphThreshold = metric.pph_threshold || 0;
29974
30004
  shiftTarget.idealOutput = metric.ideal_output || 0;
29975
30005
  shiftTarget.rank = metric.workspace_rank || 0;
30006
+ shiftTarget.idleTime = metric.idle_time || 0;
29976
30007
  });
29977
30008
  const processedData = Array.from(dayDataMap.values());
29978
30009
  console.log(`[handleMonthlyDataLoaded] Transformed data for calendar:`, {