@optifye/dashboard-core 6.10.36 → 6.10.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +153 -46
- package/dist/index.mjs +153 -46
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -4070,11 +4070,22 @@ type UseOperationalShiftKeyResult = {
|
|
|
4070
4070
|
};
|
|
4071
4071
|
declare const useOperationalShiftKey: ({ enabled, timezone, shiftConfig, pollIntervalMs, }: UseOperationalShiftKeyParams) => UseOperationalShiftKeyResult;
|
|
4072
4072
|
|
|
4073
|
-
interface
|
|
4073
|
+
interface KpiTrendGroup {
|
|
4074
4074
|
lineIds: string[];
|
|
4075
4075
|
date: string;
|
|
4076
4076
|
shiftId: number;
|
|
4077
|
+
}
|
|
4078
|
+
interface UseKpiTrendsOptions {
|
|
4079
|
+
groups: KpiTrendGroup[];
|
|
4077
4080
|
companyId?: string;
|
|
4081
|
+
/**
|
|
4082
|
+
* Optional refresh token to force re-fetch (e.g., on realtime updates).
|
|
4083
|
+
*/
|
|
4084
|
+
refreshKey?: number;
|
|
4085
|
+
/**
|
|
4086
|
+
* Optional flag to bypass backend cache.
|
|
4087
|
+
*/
|
|
4088
|
+
forceRefresh?: boolean;
|
|
4078
4089
|
}
|
|
4079
4090
|
interface UseKpiTrendsResult {
|
|
4080
4091
|
trend: KpiTrend | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -4070,11 +4070,22 @@ type UseOperationalShiftKeyResult = {
|
|
|
4070
4070
|
};
|
|
4071
4071
|
declare const useOperationalShiftKey: ({ enabled, timezone, shiftConfig, pollIntervalMs, }: UseOperationalShiftKeyParams) => UseOperationalShiftKeyResult;
|
|
4072
4072
|
|
|
4073
|
-
interface
|
|
4073
|
+
interface KpiTrendGroup {
|
|
4074
4074
|
lineIds: string[];
|
|
4075
4075
|
date: string;
|
|
4076
4076
|
shiftId: number;
|
|
4077
|
+
}
|
|
4078
|
+
interface UseKpiTrendsOptions {
|
|
4079
|
+
groups: KpiTrendGroup[];
|
|
4077
4080
|
companyId?: string;
|
|
4081
|
+
/**
|
|
4082
|
+
* Optional refresh token to force re-fetch (e.g., on realtime updates).
|
|
4083
|
+
*/
|
|
4084
|
+
refreshKey?: number;
|
|
4085
|
+
/**
|
|
4086
|
+
* Optional flag to bypass backend cache.
|
|
4087
|
+
*/
|
|
4088
|
+
forceRefresh?: boolean;
|
|
4078
4089
|
}
|
|
4079
4090
|
interface UseKpiTrendsResult {
|
|
4080
4091
|
trend: KpiTrend | null;
|
package/dist/index.js
CHANGED
|
@@ -15007,53 +15007,110 @@ var useKpiTrends = (options) => {
|
|
|
15007
15007
|
const [trend, setTrend] = React26.useState(null);
|
|
15008
15008
|
const [isLoading, setIsLoading] = React26.useState(false);
|
|
15009
15009
|
const [error, setError] = React26.useState(null);
|
|
15010
|
+
const activeRequestIdRef = React26.useRef(0);
|
|
15011
|
+
const lastParamsKeyRef = React26.useRef(null);
|
|
15010
15012
|
const params = React26.useMemo(() => {
|
|
15011
15013
|
const resolvedCompanyId = options?.companyId || entityConfig?.companyId;
|
|
15012
15014
|
if (!options || !resolvedCompanyId) return null;
|
|
15013
|
-
const
|
|
15014
|
-
|
|
15015
|
+
const normalizedGroups = (options.groups || []).map((group) => ({
|
|
15016
|
+
lineIds: (group.lineIds || []).filter(Boolean).slice().sort(),
|
|
15017
|
+
date: group.date,
|
|
15018
|
+
shiftId: group.shiftId
|
|
15019
|
+
})).filter((group) => group.lineIds.length > 0 && Boolean(group.date) && group.shiftId !== void 0 && group.shiftId !== null);
|
|
15020
|
+
if (!normalizedGroups.length) return null;
|
|
15021
|
+
const groupsKey = normalizedGroups.map((group) => `${group.date}|${group.shiftId}|${group.lineIds.join(",")}`).sort().join("||");
|
|
15015
15022
|
return {
|
|
15016
|
-
lineIds: line_ids,
|
|
15017
15023
|
companyId: resolvedCompanyId,
|
|
15018
|
-
|
|
15019
|
-
|
|
15024
|
+
groups: normalizedGroups,
|
|
15025
|
+
key: groupsKey
|
|
15020
15026
|
};
|
|
15021
15027
|
}, [options, entityConfig?.companyId]);
|
|
15022
15028
|
React26.useEffect(() => {
|
|
15023
15029
|
let isMounted = true;
|
|
15024
|
-
|
|
15030
|
+
const refreshKey = options?.refreshKey ?? 0;
|
|
15031
|
+
const paramsKey = params ? `${params.companyId}|${params.key}|${refreshKey}` : null;
|
|
15032
|
+
if (!params || !supabase || !paramsKey) {
|
|
15025
15033
|
setTrend(null);
|
|
15034
|
+
lastParamsKeyRef.current = null;
|
|
15035
|
+
return;
|
|
15036
|
+
}
|
|
15037
|
+
if (lastParamsKeyRef.current === paramsKey) {
|
|
15026
15038
|
return;
|
|
15027
15039
|
}
|
|
15040
|
+
lastParamsKeyRef.current = paramsKey;
|
|
15041
|
+
const requestId = ++activeRequestIdRef.current;
|
|
15042
|
+
const averageNumber = (values) => {
|
|
15043
|
+
const nums = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
15044
|
+
if (!nums.length) return null;
|
|
15045
|
+
return nums.reduce((sum, value) => sum + value, 0) / nums.length;
|
|
15046
|
+
};
|
|
15047
|
+
const aggregateTrends = (trends) => ({
|
|
15048
|
+
efficiency: {
|
|
15049
|
+
delta_pp: averageNumber(trends.map((item) => item.efficiency?.delta_pp)),
|
|
15050
|
+
current: averageNumber(trends.map((item) => item.efficiency?.current)),
|
|
15051
|
+
previous: averageNumber(trends.map((item) => item.efficiency?.previous))
|
|
15052
|
+
},
|
|
15053
|
+
outputProgress: {
|
|
15054
|
+
delta_pp: averageNumber(trends.map((item) => item.outputProgress?.delta_pp)),
|
|
15055
|
+
current: averageNumber(trends.map((item) => item.outputProgress?.current)),
|
|
15056
|
+
previous: averageNumber(trends.map((item) => item.outputProgress?.previous))
|
|
15057
|
+
},
|
|
15058
|
+
underperformingWorkers: {
|
|
15059
|
+
delta_count: averageNumber(trends.map((item) => item.underperformingWorkers?.delta_count)),
|
|
15060
|
+
current: averageNumber(trends.map((item) => item.underperformingWorkers?.current)),
|
|
15061
|
+
previous: averageNumber(trends.map((item) => item.underperformingWorkers?.previous))
|
|
15062
|
+
},
|
|
15063
|
+
avgCycleTime: {
|
|
15064
|
+
delta_seconds: averageNumber(trends.map((item) => item.avgCycleTime?.delta_seconds)),
|
|
15065
|
+
current: averageNumber(trends.map((item) => item.avgCycleTime?.current)),
|
|
15066
|
+
previous: averageNumber(trends.map((item) => item.avgCycleTime?.previous))
|
|
15067
|
+
}
|
|
15068
|
+
});
|
|
15028
15069
|
const fetchTrend = async () => {
|
|
15029
15070
|
setIsLoading(true);
|
|
15030
15071
|
setError(null);
|
|
15031
15072
|
try {
|
|
15032
|
-
const
|
|
15033
|
-
|
|
15034
|
-
|
|
15073
|
+
const forceRefresh = options?.forceRefresh;
|
|
15074
|
+
const groupResults = await Promise.all(
|
|
15075
|
+
params.groups.map(async (group) => {
|
|
15076
|
+
const searchParams = new URLSearchParams();
|
|
15077
|
+
if (group.lineIds.length === 1) {
|
|
15078
|
+
searchParams.append("line_id", group.lineIds[0]);
|
|
15079
|
+
} else {
|
|
15080
|
+
searchParams.append("line_ids", group.lineIds.join(","));
|
|
15081
|
+
}
|
|
15082
|
+
searchParams.append("date", group.date);
|
|
15083
|
+
searchParams.append("shift_id", group.shiftId.toString());
|
|
15084
|
+
searchParams.append("company_id", params.companyId);
|
|
15085
|
+
if (forceRefresh) {
|
|
15086
|
+
searchParams.append("force_refresh", "true");
|
|
15087
|
+
}
|
|
15088
|
+
const response = await fetchBackendJson(supabase, `/api/dashboard/line-kpis?${searchParams.toString()}`);
|
|
15089
|
+
return response?.kpis?.trend || null;
|
|
15090
|
+
})
|
|
15091
|
+
);
|
|
15092
|
+
if (!isMounted || requestId !== activeRequestIdRef.current) return;
|
|
15093
|
+
const trends = groupResults.filter((item) => Boolean(item));
|
|
15094
|
+
if (!trends.length) {
|
|
15095
|
+
setTrend(null);
|
|
15096
|
+
} else if (trends.length === 1) {
|
|
15097
|
+
setTrend(trends[0]);
|
|
15035
15098
|
} else {
|
|
15036
|
-
|
|
15099
|
+
setTrend(aggregateTrends(trends));
|
|
15037
15100
|
}
|
|
15038
|
-
searchParams.append("date", params.date);
|
|
15039
|
-
searchParams.append("shift_id", params.shiftId.toString());
|
|
15040
|
-
searchParams.append("company_id", params.companyId);
|
|
15041
|
-
const response = await fetchBackendJson(supabase, `/api/dashboard/line-kpis?${searchParams.toString()}`);
|
|
15042
|
-
if (!isMounted) return;
|
|
15043
|
-
setTrend(response?.kpis?.trend || null);
|
|
15044
15101
|
} catch (err) {
|
|
15045
|
-
if (!isMounted) return;
|
|
15102
|
+
if (!isMounted || requestId !== activeRequestIdRef.current) return;
|
|
15046
15103
|
setError(err);
|
|
15047
15104
|
setTrend(null);
|
|
15048
15105
|
} finally {
|
|
15049
|
-
if (isMounted) setIsLoading(false);
|
|
15106
|
+
if (isMounted && requestId === activeRequestIdRef.current) setIsLoading(false);
|
|
15050
15107
|
}
|
|
15051
15108
|
};
|
|
15052
15109
|
fetchTrend();
|
|
15053
15110
|
return () => {
|
|
15054
15111
|
isMounted = false;
|
|
15055
15112
|
};
|
|
15056
|
-
}, [params, supabase]);
|
|
15113
|
+
}, [params, supabase, options?.refreshKey, options?.forceRefresh]);
|
|
15057
15114
|
return { trend, isLoading, error };
|
|
15058
15115
|
};
|
|
15059
15116
|
var useMonthlyTrend = (params) => {
|
|
@@ -17020,12 +17077,16 @@ var aggregateKPIsFromLineMetricsRows = (rows) => {
|
|
|
17020
17077
|
(sum, row) => sum + (toNumber(row.ideal_output) || toNumber(row.line_threshold)),
|
|
17021
17078
|
0
|
|
17022
17079
|
);
|
|
17023
|
-
const
|
|
17024
|
-
|
|
17025
|
-
(
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17080
|
+
const efficiencyValues = rows.map((row) => {
|
|
17081
|
+
const value = row?.avg_efficiency;
|
|
17082
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
17083
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
17084
|
+
const parsed = Number(value);
|
|
17085
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
17086
|
+
}
|
|
17087
|
+
return null;
|
|
17088
|
+
}).filter((value) => value !== null);
|
|
17089
|
+
const avgEfficiency = efficiencyValues.length > 0 ? efficiencyValues.reduce((sum, value) => sum + value, 0) / efficiencyValues.length : 0;
|
|
17029
17090
|
const numLines = rows.length;
|
|
17030
17091
|
const avgCycleTime = numLines > 0 ? rows.reduce((sum, row) => sum + toNumber(row.avg_cycle_time), 0) / numLines : 0;
|
|
17031
17092
|
const totalUnderperforming = rows.reduce((sum, row) => sum + toNumber(row.underperforming_workspaces), 0);
|
|
@@ -45786,7 +45847,7 @@ var KPICard = ({
|
|
|
45786
45847
|
style.compact ? "text-base" : "text-lg"
|
|
45787
45848
|
), children: suffix })
|
|
45788
45849
|
] }),
|
|
45789
|
-
!isLoading && trendInfo.shouldShowTrend &&
|
|
45850
|
+
!isLoading && trendInfo.shouldShowTrend && title !== "Output" && ((trendInfo.trendValue !== "neutral" || change === 0 && showZeroChange) && (trendMode === "pill" ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: clsx(
|
|
45790
45851
|
"flex items-center gap-1 px-2 py-0.5 rounded-full ml-2",
|
|
45791
45852
|
trendInfo.bgClass,
|
|
45792
45853
|
trendInfo.colorClass
|
|
@@ -45823,7 +45884,7 @@ var KPICard = ({
|
|
|
45823
45884
|
formattedChange,
|
|
45824
45885
|
"%"
|
|
45825
45886
|
] })
|
|
45826
|
-
] })),
|
|
45887
|
+
] }))),
|
|
45827
45888
|
status?.indicator && !isLoading && /* @__PURE__ */ jsxRuntime.jsx(
|
|
45828
45889
|
"div",
|
|
45829
45890
|
{
|
|
@@ -45981,7 +46042,7 @@ var KPISection = React26.memo(({
|
|
|
45981
46042
|
const outputIsOnTarget = outputDifference >= 0;
|
|
45982
46043
|
if (useSrcLayout) {
|
|
45983
46044
|
const effChange = showSkeleton ? 0 : kpis.efficiency.change ?? 0;
|
|
45984
|
-
const effTrend = effChange
|
|
46045
|
+
const effTrend = effChange > 0 ? "up" : effChange < 0 ? "down" : "neutral";
|
|
45985
46046
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
45986
46047
|
"div",
|
|
45987
46048
|
{
|
|
@@ -46079,8 +46140,11 @@ var KPISection = React26.memo(({
|
|
|
46079
46140
|
if (!prevKpis || !nextKpis) return false;
|
|
46080
46141
|
if (prevKpis === nextKpis) return true;
|
|
46081
46142
|
if (Math.abs(prevKpis.efficiency.value - nextKpis.efficiency.value) >= 0.5) return false;
|
|
46143
|
+
if (prevKpis.efficiency.change !== nextKpis.efficiency.change) return false;
|
|
46082
46144
|
if (prevKpis.underperformingWorkers.current !== nextKpis.underperformingWorkers.current || prevKpis.underperformingWorkers.total !== nextKpis.underperformingWorkers.total) return false;
|
|
46145
|
+
if (prevKpis.underperformingWorkers.change !== nextKpis.underperformingWorkers.change) return false;
|
|
46083
46146
|
if (prevKpis.outputProgress.current !== nextKpis.outputProgress.current || prevKpis.outputProgress.target !== nextKpis.outputProgress.target) return false;
|
|
46147
|
+
if (prevKpis.outputProgress.change !== nextKpis.outputProgress.change) return false;
|
|
46084
46148
|
if (prevProps.layout !== nextProps.layout || prevProps.cardVariant !== nextProps.cardVariant || prevProps.compactCards !== nextProps.compactCards || prevProps.useSrcLayout !== nextProps.useSrcLayout || prevProps.className !== nextProps.className) return false;
|
|
46085
46149
|
return true;
|
|
46086
46150
|
});
|
|
@@ -53666,6 +53730,8 @@ function HomeView({
|
|
|
53666
53730
|
const [hasInitialDataLoaded, setHasInitialDataLoaded] = React26.useState(false);
|
|
53667
53731
|
const [showDataLoading, setShowDataLoading] = React26.useState(false);
|
|
53668
53732
|
const loadingStartRef = React26.useRef(null);
|
|
53733
|
+
const [trendRefreshKey, setTrendRefreshKey] = React26.useState(0);
|
|
53734
|
+
const trendRefreshTimerRef = React26.useRef(null);
|
|
53669
53735
|
const dashboardConfig = useDashboardConfig();
|
|
53670
53736
|
const entityConfig = useEntityConfig();
|
|
53671
53737
|
const supabaseClient = useSupabaseClient();
|
|
@@ -53816,6 +53882,20 @@ function HomeView({
|
|
|
53816
53882
|
loading: displayNamesLoading,
|
|
53817
53883
|
error: displayNamesError
|
|
53818
53884
|
} = useWorkspaceDisplayNames(displayNameLineId, void 0);
|
|
53885
|
+
const handleLineMetricsUpdate = React26.useCallback(() => {
|
|
53886
|
+
if (trendRefreshTimerRef.current) {
|
|
53887
|
+
window.clearTimeout(trendRefreshTimerRef.current);
|
|
53888
|
+
}
|
|
53889
|
+
trendRefreshTimerRef.current = window.setTimeout(() => {
|
|
53890
|
+
setTrendRefreshKey((prev) => prev + 1);
|
|
53891
|
+
trendRefreshTimerRef.current = null;
|
|
53892
|
+
}, 2e3);
|
|
53893
|
+
}, []);
|
|
53894
|
+
React26.useEffect(() => () => {
|
|
53895
|
+
if (trendRefreshTimerRef.current) {
|
|
53896
|
+
window.clearTimeout(trendRefreshTimerRef.current);
|
|
53897
|
+
}
|
|
53898
|
+
}, []);
|
|
53819
53899
|
const {
|
|
53820
53900
|
workspaceMetrics,
|
|
53821
53901
|
lineMetrics,
|
|
@@ -53826,32 +53906,59 @@ function HomeView({
|
|
|
53826
53906
|
refetch: refetchMetrics
|
|
53827
53907
|
} = useDashboardMetrics({
|
|
53828
53908
|
lineId: selectedLineId,
|
|
53909
|
+
onLineMetricsUpdate: handleLineMetricsUpdate,
|
|
53829
53910
|
userAccessibleLineIds: visibleLineIds
|
|
53830
53911
|
// Pass user's accessible lines for supervisor filtering
|
|
53831
53912
|
});
|
|
53832
|
-
const
|
|
53833
|
-
const defaultTimezone = appTimezone || dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
|
|
53834
|
-
const { shiftId: fallbackShiftId, date: fallbackDate } = React26.useMemo(
|
|
53835
|
-
() => getCurrentShift(defaultTimezone, dashboardConfig?.shiftConfig),
|
|
53836
|
-
[defaultTimezone, dashboardConfig?.shiftConfig]
|
|
53837
|
-
);
|
|
53838
|
-
const trendQuery = React26.useMemo(() => {
|
|
53913
|
+
const trendGroups = React26.useMemo(() => {
|
|
53839
53914
|
const lineMetricsRows = lineMetrics || [];
|
|
53840
|
-
|
|
53841
|
-
|
|
53842
|
-
|
|
53843
|
-
|
|
53844
|
-
|
|
53915
|
+
if (!lineMetricsRows.length) return null;
|
|
53916
|
+
if (selectedLineId === factoryViewId) {
|
|
53917
|
+
const candidateLineIds = visibleLineIds.filter((id3) => id3 && id3 !== factoryViewId);
|
|
53918
|
+
if (!candidateLineIds.length) return null;
|
|
53919
|
+
const rowsForLines = lineMetricsRows.filter((row2) => candidateLineIds.includes(row2?.line_id));
|
|
53920
|
+
if (!rowsForLines.length) return null;
|
|
53921
|
+
const groupsMap = /* @__PURE__ */ new Map();
|
|
53922
|
+
rowsForLines.forEach((row2) => {
|
|
53923
|
+
const lineId = row2?.line_id;
|
|
53924
|
+
const date = row2?.date;
|
|
53925
|
+
const shiftId = row2?.shift_id;
|
|
53926
|
+
if (!lineId || !date || shiftId === void 0 || shiftId === null) return;
|
|
53927
|
+
const key = `${date}|${shiftId}`;
|
|
53928
|
+
let group = groupsMap.get(key);
|
|
53929
|
+
if (!group) {
|
|
53930
|
+
group = { date, shiftId, lineIds: /* @__PURE__ */ new Set() };
|
|
53931
|
+
groupsMap.set(key, group);
|
|
53932
|
+
}
|
|
53933
|
+
group.lineIds.add(lineId);
|
|
53934
|
+
});
|
|
53935
|
+
if (!groupsMap.size) return null;
|
|
53936
|
+
return Array.from(groupsMap.values()).map((group) => ({
|
|
53937
|
+
lineIds: Array.from(group.lineIds),
|
|
53938
|
+
date: group.date,
|
|
53939
|
+
shiftId: group.shiftId
|
|
53940
|
+
}));
|
|
53941
|
+
}
|
|
53942
|
+
const row = lineMetricsRows.find((r2) => r2?.line_id === selectedLineId);
|
|
53943
|
+
if (!row?.date || row?.shift_id === void 0 || row?.shift_id === null) {
|
|
53845
53944
|
return null;
|
|
53846
53945
|
}
|
|
53946
|
+
return [{
|
|
53947
|
+
lineIds: [selectedLineId],
|
|
53948
|
+
date: row.date,
|
|
53949
|
+
shiftId: row.shift_id
|
|
53950
|
+
}];
|
|
53951
|
+
}, [selectedLineId, factoryViewId, visibleLineIds, lineMetrics]);
|
|
53952
|
+
const trendOptions = React26.useMemo(() => {
|
|
53953
|
+
if (!trendGroups || !userCompanyId) return null;
|
|
53847
53954
|
return {
|
|
53848
|
-
|
|
53849
|
-
|
|
53850
|
-
|
|
53851
|
-
|
|
53955
|
+
groups: trendGroups,
|
|
53956
|
+
companyId: userCompanyId,
|
|
53957
|
+
refreshKey: trendRefreshKey,
|
|
53958
|
+
forceRefresh: trendRefreshKey > 0
|
|
53852
53959
|
};
|
|
53853
|
-
}, [
|
|
53854
|
-
const { trend: kpiTrend } = useKpiTrends(
|
|
53960
|
+
}, [trendGroups, userCompanyId, trendRefreshKey]);
|
|
53961
|
+
const { trend: kpiTrend } = useKpiTrends(trendOptions);
|
|
53855
53962
|
const hasFlowBuffers = Boolean(metricsMetadata?.hasFlowBuffers);
|
|
53856
53963
|
React26.useEffect(() => {
|
|
53857
53964
|
const sample = workspaceMetrics.slice(0, 3).map((workspace) => ({
|
package/dist/index.mjs
CHANGED
|
@@ -14978,53 +14978,110 @@ var useKpiTrends = (options) => {
|
|
|
14978
14978
|
const [trend, setTrend] = useState(null);
|
|
14979
14979
|
const [isLoading, setIsLoading] = useState(false);
|
|
14980
14980
|
const [error, setError] = useState(null);
|
|
14981
|
+
const activeRequestIdRef = useRef(0);
|
|
14982
|
+
const lastParamsKeyRef = useRef(null);
|
|
14981
14983
|
const params = useMemo(() => {
|
|
14982
14984
|
const resolvedCompanyId = options?.companyId || entityConfig?.companyId;
|
|
14983
14985
|
if (!options || !resolvedCompanyId) return null;
|
|
14984
|
-
const
|
|
14985
|
-
|
|
14986
|
+
const normalizedGroups = (options.groups || []).map((group) => ({
|
|
14987
|
+
lineIds: (group.lineIds || []).filter(Boolean).slice().sort(),
|
|
14988
|
+
date: group.date,
|
|
14989
|
+
shiftId: group.shiftId
|
|
14990
|
+
})).filter((group) => group.lineIds.length > 0 && Boolean(group.date) && group.shiftId !== void 0 && group.shiftId !== null);
|
|
14991
|
+
if (!normalizedGroups.length) return null;
|
|
14992
|
+
const groupsKey = normalizedGroups.map((group) => `${group.date}|${group.shiftId}|${group.lineIds.join(",")}`).sort().join("||");
|
|
14986
14993
|
return {
|
|
14987
|
-
lineIds: line_ids,
|
|
14988
14994
|
companyId: resolvedCompanyId,
|
|
14989
|
-
|
|
14990
|
-
|
|
14995
|
+
groups: normalizedGroups,
|
|
14996
|
+
key: groupsKey
|
|
14991
14997
|
};
|
|
14992
14998
|
}, [options, entityConfig?.companyId]);
|
|
14993
14999
|
useEffect(() => {
|
|
14994
15000
|
let isMounted = true;
|
|
14995
|
-
|
|
15001
|
+
const refreshKey = options?.refreshKey ?? 0;
|
|
15002
|
+
const paramsKey = params ? `${params.companyId}|${params.key}|${refreshKey}` : null;
|
|
15003
|
+
if (!params || !supabase || !paramsKey) {
|
|
14996
15004
|
setTrend(null);
|
|
15005
|
+
lastParamsKeyRef.current = null;
|
|
15006
|
+
return;
|
|
15007
|
+
}
|
|
15008
|
+
if (lastParamsKeyRef.current === paramsKey) {
|
|
14997
15009
|
return;
|
|
14998
15010
|
}
|
|
15011
|
+
lastParamsKeyRef.current = paramsKey;
|
|
15012
|
+
const requestId = ++activeRequestIdRef.current;
|
|
15013
|
+
const averageNumber = (values) => {
|
|
15014
|
+
const nums = values.filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
15015
|
+
if (!nums.length) return null;
|
|
15016
|
+
return nums.reduce((sum, value) => sum + value, 0) / nums.length;
|
|
15017
|
+
};
|
|
15018
|
+
const aggregateTrends = (trends) => ({
|
|
15019
|
+
efficiency: {
|
|
15020
|
+
delta_pp: averageNumber(trends.map((item) => item.efficiency?.delta_pp)),
|
|
15021
|
+
current: averageNumber(trends.map((item) => item.efficiency?.current)),
|
|
15022
|
+
previous: averageNumber(trends.map((item) => item.efficiency?.previous))
|
|
15023
|
+
},
|
|
15024
|
+
outputProgress: {
|
|
15025
|
+
delta_pp: averageNumber(trends.map((item) => item.outputProgress?.delta_pp)),
|
|
15026
|
+
current: averageNumber(trends.map((item) => item.outputProgress?.current)),
|
|
15027
|
+
previous: averageNumber(trends.map((item) => item.outputProgress?.previous))
|
|
15028
|
+
},
|
|
15029
|
+
underperformingWorkers: {
|
|
15030
|
+
delta_count: averageNumber(trends.map((item) => item.underperformingWorkers?.delta_count)),
|
|
15031
|
+
current: averageNumber(trends.map((item) => item.underperformingWorkers?.current)),
|
|
15032
|
+
previous: averageNumber(trends.map((item) => item.underperformingWorkers?.previous))
|
|
15033
|
+
},
|
|
15034
|
+
avgCycleTime: {
|
|
15035
|
+
delta_seconds: averageNumber(trends.map((item) => item.avgCycleTime?.delta_seconds)),
|
|
15036
|
+
current: averageNumber(trends.map((item) => item.avgCycleTime?.current)),
|
|
15037
|
+
previous: averageNumber(trends.map((item) => item.avgCycleTime?.previous))
|
|
15038
|
+
}
|
|
15039
|
+
});
|
|
14999
15040
|
const fetchTrend = async () => {
|
|
15000
15041
|
setIsLoading(true);
|
|
15001
15042
|
setError(null);
|
|
15002
15043
|
try {
|
|
15003
|
-
const
|
|
15004
|
-
|
|
15005
|
-
|
|
15044
|
+
const forceRefresh = options?.forceRefresh;
|
|
15045
|
+
const groupResults = await Promise.all(
|
|
15046
|
+
params.groups.map(async (group) => {
|
|
15047
|
+
const searchParams = new URLSearchParams();
|
|
15048
|
+
if (group.lineIds.length === 1) {
|
|
15049
|
+
searchParams.append("line_id", group.lineIds[0]);
|
|
15050
|
+
} else {
|
|
15051
|
+
searchParams.append("line_ids", group.lineIds.join(","));
|
|
15052
|
+
}
|
|
15053
|
+
searchParams.append("date", group.date);
|
|
15054
|
+
searchParams.append("shift_id", group.shiftId.toString());
|
|
15055
|
+
searchParams.append("company_id", params.companyId);
|
|
15056
|
+
if (forceRefresh) {
|
|
15057
|
+
searchParams.append("force_refresh", "true");
|
|
15058
|
+
}
|
|
15059
|
+
const response = await fetchBackendJson(supabase, `/api/dashboard/line-kpis?${searchParams.toString()}`);
|
|
15060
|
+
return response?.kpis?.trend || null;
|
|
15061
|
+
})
|
|
15062
|
+
);
|
|
15063
|
+
if (!isMounted || requestId !== activeRequestIdRef.current) return;
|
|
15064
|
+
const trends = groupResults.filter((item) => Boolean(item));
|
|
15065
|
+
if (!trends.length) {
|
|
15066
|
+
setTrend(null);
|
|
15067
|
+
} else if (trends.length === 1) {
|
|
15068
|
+
setTrend(trends[0]);
|
|
15006
15069
|
} else {
|
|
15007
|
-
|
|
15070
|
+
setTrend(aggregateTrends(trends));
|
|
15008
15071
|
}
|
|
15009
|
-
searchParams.append("date", params.date);
|
|
15010
|
-
searchParams.append("shift_id", params.shiftId.toString());
|
|
15011
|
-
searchParams.append("company_id", params.companyId);
|
|
15012
|
-
const response = await fetchBackendJson(supabase, `/api/dashboard/line-kpis?${searchParams.toString()}`);
|
|
15013
|
-
if (!isMounted) return;
|
|
15014
|
-
setTrend(response?.kpis?.trend || null);
|
|
15015
15072
|
} catch (err) {
|
|
15016
|
-
if (!isMounted) return;
|
|
15073
|
+
if (!isMounted || requestId !== activeRequestIdRef.current) return;
|
|
15017
15074
|
setError(err);
|
|
15018
15075
|
setTrend(null);
|
|
15019
15076
|
} finally {
|
|
15020
|
-
if (isMounted) setIsLoading(false);
|
|
15077
|
+
if (isMounted && requestId === activeRequestIdRef.current) setIsLoading(false);
|
|
15021
15078
|
}
|
|
15022
15079
|
};
|
|
15023
15080
|
fetchTrend();
|
|
15024
15081
|
return () => {
|
|
15025
15082
|
isMounted = false;
|
|
15026
15083
|
};
|
|
15027
|
-
}, [params, supabase]);
|
|
15084
|
+
}, [params, supabase, options?.refreshKey, options?.forceRefresh]);
|
|
15028
15085
|
return { trend, isLoading, error };
|
|
15029
15086
|
};
|
|
15030
15087
|
var useMonthlyTrend = (params) => {
|
|
@@ -16991,12 +17048,16 @@ var aggregateKPIsFromLineMetricsRows = (rows) => {
|
|
|
16991
17048
|
(sum, row) => sum + (toNumber(row.ideal_output) || toNumber(row.line_threshold)),
|
|
16992
17049
|
0
|
|
16993
17050
|
);
|
|
16994
|
-
const
|
|
16995
|
-
|
|
16996
|
-
(
|
|
16997
|
-
|
|
16998
|
-
|
|
16999
|
-
|
|
17051
|
+
const efficiencyValues = rows.map((row) => {
|
|
17052
|
+
const value = row?.avg_efficiency;
|
|
17053
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
17054
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
17055
|
+
const parsed = Number(value);
|
|
17056
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
17057
|
+
}
|
|
17058
|
+
return null;
|
|
17059
|
+
}).filter((value) => value !== null);
|
|
17060
|
+
const avgEfficiency = efficiencyValues.length > 0 ? efficiencyValues.reduce((sum, value) => sum + value, 0) / efficiencyValues.length : 0;
|
|
17000
17061
|
const numLines = rows.length;
|
|
17001
17062
|
const avgCycleTime = numLines > 0 ? rows.reduce((sum, row) => sum + toNumber(row.avg_cycle_time), 0) / numLines : 0;
|
|
17002
17063
|
const totalUnderperforming = rows.reduce((sum, row) => sum + toNumber(row.underperforming_workspaces), 0);
|
|
@@ -45757,7 +45818,7 @@ var KPICard = ({
|
|
|
45757
45818
|
style.compact ? "text-base" : "text-lg"
|
|
45758
45819
|
), children: suffix })
|
|
45759
45820
|
] }),
|
|
45760
|
-
!isLoading && trendInfo.shouldShowTrend &&
|
|
45821
|
+
!isLoading && trendInfo.shouldShowTrend && title !== "Output" && ((trendInfo.trendValue !== "neutral" || change === 0 && showZeroChange) && (trendMode === "pill" ? /* @__PURE__ */ jsxs("span", { className: clsx(
|
|
45761
45822
|
"flex items-center gap-1 px-2 py-0.5 rounded-full ml-2",
|
|
45762
45823
|
trendInfo.bgClass,
|
|
45763
45824
|
trendInfo.colorClass
|
|
@@ -45794,7 +45855,7 @@ var KPICard = ({
|
|
|
45794
45855
|
formattedChange,
|
|
45795
45856
|
"%"
|
|
45796
45857
|
] })
|
|
45797
|
-
] })),
|
|
45858
|
+
] }))),
|
|
45798
45859
|
status?.indicator && !isLoading && /* @__PURE__ */ jsx(
|
|
45799
45860
|
"div",
|
|
45800
45861
|
{
|
|
@@ -45952,7 +46013,7 @@ var KPISection = memo$1(({
|
|
|
45952
46013
|
const outputIsOnTarget = outputDifference >= 0;
|
|
45953
46014
|
if (useSrcLayout) {
|
|
45954
46015
|
const effChange = showSkeleton ? 0 : kpis.efficiency.change ?? 0;
|
|
45955
|
-
const effTrend = effChange
|
|
46016
|
+
const effTrend = effChange > 0 ? "up" : effChange < 0 ? "down" : "neutral";
|
|
45956
46017
|
return /* @__PURE__ */ jsxs(
|
|
45957
46018
|
"div",
|
|
45958
46019
|
{
|
|
@@ -46050,8 +46111,11 @@ var KPISection = memo$1(({
|
|
|
46050
46111
|
if (!prevKpis || !nextKpis) return false;
|
|
46051
46112
|
if (prevKpis === nextKpis) return true;
|
|
46052
46113
|
if (Math.abs(prevKpis.efficiency.value - nextKpis.efficiency.value) >= 0.5) return false;
|
|
46114
|
+
if (prevKpis.efficiency.change !== nextKpis.efficiency.change) return false;
|
|
46053
46115
|
if (prevKpis.underperformingWorkers.current !== nextKpis.underperformingWorkers.current || prevKpis.underperformingWorkers.total !== nextKpis.underperformingWorkers.total) return false;
|
|
46116
|
+
if (prevKpis.underperformingWorkers.change !== nextKpis.underperformingWorkers.change) return false;
|
|
46054
46117
|
if (prevKpis.outputProgress.current !== nextKpis.outputProgress.current || prevKpis.outputProgress.target !== nextKpis.outputProgress.target) return false;
|
|
46118
|
+
if (prevKpis.outputProgress.change !== nextKpis.outputProgress.change) return false;
|
|
46055
46119
|
if (prevProps.layout !== nextProps.layout || prevProps.cardVariant !== nextProps.cardVariant || prevProps.compactCards !== nextProps.compactCards || prevProps.useSrcLayout !== nextProps.useSrcLayout || prevProps.className !== nextProps.className) return false;
|
|
46056
46120
|
return true;
|
|
46057
46121
|
});
|
|
@@ -53637,6 +53701,8 @@ function HomeView({
|
|
|
53637
53701
|
const [hasInitialDataLoaded, setHasInitialDataLoaded] = useState(false);
|
|
53638
53702
|
const [showDataLoading, setShowDataLoading] = useState(false);
|
|
53639
53703
|
const loadingStartRef = useRef(null);
|
|
53704
|
+
const [trendRefreshKey, setTrendRefreshKey] = useState(0);
|
|
53705
|
+
const trendRefreshTimerRef = useRef(null);
|
|
53640
53706
|
const dashboardConfig = useDashboardConfig();
|
|
53641
53707
|
const entityConfig = useEntityConfig();
|
|
53642
53708
|
const supabaseClient = useSupabaseClient();
|
|
@@ -53787,6 +53853,20 @@ function HomeView({
|
|
|
53787
53853
|
loading: displayNamesLoading,
|
|
53788
53854
|
error: displayNamesError
|
|
53789
53855
|
} = useWorkspaceDisplayNames(displayNameLineId, void 0);
|
|
53856
|
+
const handleLineMetricsUpdate = useCallback(() => {
|
|
53857
|
+
if (trendRefreshTimerRef.current) {
|
|
53858
|
+
window.clearTimeout(trendRefreshTimerRef.current);
|
|
53859
|
+
}
|
|
53860
|
+
trendRefreshTimerRef.current = window.setTimeout(() => {
|
|
53861
|
+
setTrendRefreshKey((prev) => prev + 1);
|
|
53862
|
+
trendRefreshTimerRef.current = null;
|
|
53863
|
+
}, 2e3);
|
|
53864
|
+
}, []);
|
|
53865
|
+
useEffect(() => () => {
|
|
53866
|
+
if (trendRefreshTimerRef.current) {
|
|
53867
|
+
window.clearTimeout(trendRefreshTimerRef.current);
|
|
53868
|
+
}
|
|
53869
|
+
}, []);
|
|
53790
53870
|
const {
|
|
53791
53871
|
workspaceMetrics,
|
|
53792
53872
|
lineMetrics,
|
|
@@ -53797,32 +53877,59 @@ function HomeView({
|
|
|
53797
53877
|
refetch: refetchMetrics
|
|
53798
53878
|
} = useDashboardMetrics({
|
|
53799
53879
|
lineId: selectedLineId,
|
|
53880
|
+
onLineMetricsUpdate: handleLineMetricsUpdate,
|
|
53800
53881
|
userAccessibleLineIds: visibleLineIds
|
|
53801
53882
|
// Pass user's accessible lines for supervisor filtering
|
|
53802
53883
|
});
|
|
53803
|
-
const
|
|
53804
|
-
const defaultTimezone = appTimezone || dashboardConfig?.dateTimeConfig?.defaultTimezone || "UTC";
|
|
53805
|
-
const { shiftId: fallbackShiftId, date: fallbackDate } = useMemo(
|
|
53806
|
-
() => getCurrentShift(defaultTimezone, dashboardConfig?.shiftConfig),
|
|
53807
|
-
[defaultTimezone, dashboardConfig?.shiftConfig]
|
|
53808
|
-
);
|
|
53809
|
-
const trendQuery = useMemo(() => {
|
|
53884
|
+
const trendGroups = useMemo(() => {
|
|
53810
53885
|
const lineMetricsRows = lineMetrics || [];
|
|
53811
|
-
|
|
53812
|
-
|
|
53813
|
-
|
|
53814
|
-
|
|
53815
|
-
|
|
53886
|
+
if (!lineMetricsRows.length) return null;
|
|
53887
|
+
if (selectedLineId === factoryViewId) {
|
|
53888
|
+
const candidateLineIds = visibleLineIds.filter((id3) => id3 && id3 !== factoryViewId);
|
|
53889
|
+
if (!candidateLineIds.length) return null;
|
|
53890
|
+
const rowsForLines = lineMetricsRows.filter((row2) => candidateLineIds.includes(row2?.line_id));
|
|
53891
|
+
if (!rowsForLines.length) return null;
|
|
53892
|
+
const groupsMap = /* @__PURE__ */ new Map();
|
|
53893
|
+
rowsForLines.forEach((row2) => {
|
|
53894
|
+
const lineId = row2?.line_id;
|
|
53895
|
+
const date = row2?.date;
|
|
53896
|
+
const shiftId = row2?.shift_id;
|
|
53897
|
+
if (!lineId || !date || shiftId === void 0 || shiftId === null) return;
|
|
53898
|
+
const key = `${date}|${shiftId}`;
|
|
53899
|
+
let group = groupsMap.get(key);
|
|
53900
|
+
if (!group) {
|
|
53901
|
+
group = { date, shiftId, lineIds: /* @__PURE__ */ new Set() };
|
|
53902
|
+
groupsMap.set(key, group);
|
|
53903
|
+
}
|
|
53904
|
+
group.lineIds.add(lineId);
|
|
53905
|
+
});
|
|
53906
|
+
if (!groupsMap.size) return null;
|
|
53907
|
+
return Array.from(groupsMap.values()).map((group) => ({
|
|
53908
|
+
lineIds: Array.from(group.lineIds),
|
|
53909
|
+
date: group.date,
|
|
53910
|
+
shiftId: group.shiftId
|
|
53911
|
+
}));
|
|
53912
|
+
}
|
|
53913
|
+
const row = lineMetricsRows.find((r2) => r2?.line_id === selectedLineId);
|
|
53914
|
+
if (!row?.date || row?.shift_id === void 0 || row?.shift_id === null) {
|
|
53816
53915
|
return null;
|
|
53817
53916
|
}
|
|
53917
|
+
return [{
|
|
53918
|
+
lineIds: [selectedLineId],
|
|
53919
|
+
date: row.date,
|
|
53920
|
+
shiftId: row.shift_id
|
|
53921
|
+
}];
|
|
53922
|
+
}, [selectedLineId, factoryViewId, visibleLineIds, lineMetrics]);
|
|
53923
|
+
const trendOptions = useMemo(() => {
|
|
53924
|
+
if (!trendGroups || !userCompanyId) return null;
|
|
53818
53925
|
return {
|
|
53819
|
-
|
|
53820
|
-
|
|
53821
|
-
|
|
53822
|
-
|
|
53926
|
+
groups: trendGroups,
|
|
53927
|
+
companyId: userCompanyId,
|
|
53928
|
+
refreshKey: trendRefreshKey,
|
|
53929
|
+
forceRefresh: trendRefreshKey > 0
|
|
53823
53930
|
};
|
|
53824
|
-
}, [
|
|
53825
|
-
const { trend: kpiTrend } = useKpiTrends(
|
|
53931
|
+
}, [trendGroups, userCompanyId, trendRefreshKey]);
|
|
53932
|
+
const { trend: kpiTrend } = useKpiTrends(trendOptions);
|
|
53826
53933
|
const hasFlowBuffers = Boolean(metricsMetadata?.hasFlowBuffers);
|
|
53827
53934
|
useEffect(() => {
|
|
53828
53935
|
const sample = workspaceMetrics.slice(0, 3).map((workspace) => ({
|