@optifye/dashboard-core 6.10.5 → 6.10.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.css +31 -0
- package/dist/index.d.mts +37 -23
- package/dist/index.d.ts +37 -23
- package/dist/index.js +1209 -610
- package/dist/index.mjs +1211 -612
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3757,18 +3757,17 @@ var initializeCoreMixpanel = (token, debugOrOptions, trackPageViewArg) => {
|
|
|
3757
3757
|
track_pageview: trackPageView ?? true,
|
|
3758
3758
|
persistence: "localStorage"
|
|
3759
3759
|
};
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
initOptions.record_idle_timeout_ms = sessionOpts.recordIdleTimeoutMs;
|
|
3760
|
+
const recordSessionsPercent = sessionOpts.recordSessionsPercent ?? 0;
|
|
3761
|
+
initOptions.record_sessions_percent = recordSessionsPercent;
|
|
3762
|
+
const defaultIdleTimeoutMs = 10 * 60 * 1e3;
|
|
3763
|
+
const recordIdleTimeoutMs = sessionOpts.recordIdleTimeoutMs ?? (recordSessionsPercent > 0 ? defaultIdleTimeoutMs : void 0);
|
|
3764
|
+
if (recordIdleTimeoutMs !== void 0) {
|
|
3765
|
+
initOptions.record_idle_timeout_ms = recordIdleTimeoutMs;
|
|
3767
3766
|
}
|
|
3768
3767
|
if (sessionOpts.recordHeatmapData !== void 0) {
|
|
3769
3768
|
initOptions.record_heatmap_data = sessionOpts.recordHeatmapData;
|
|
3770
3769
|
} else {
|
|
3771
|
-
initOptions.record_heatmap_data =
|
|
3770
|
+
initOptions.record_heatmap_data = false;
|
|
3772
3771
|
}
|
|
3773
3772
|
if (sessionOpts.recordCanvas !== void 0) {
|
|
3774
3773
|
initOptions.record_canvas = sessionOpts.recordCanvas;
|
|
@@ -3786,7 +3785,9 @@ var initializeCoreMixpanel = (token, debugOrOptions, trackPageViewArg) => {
|
|
|
3786
3785
|
});
|
|
3787
3786
|
mixpanel__default.default.init(token, initOptions);
|
|
3788
3787
|
isMixpanelInitialized = true;
|
|
3789
|
-
|
|
3788
|
+
if (initOptions.debug) {
|
|
3789
|
+
console.log("Mixpanel initialized in dashboard-core.");
|
|
3790
|
+
}
|
|
3790
3791
|
};
|
|
3791
3792
|
var trackCorePageView = (pageName, properties) => {
|
|
3792
3793
|
if (!isMixpanelInitialized) return;
|
|
@@ -10330,6 +10331,41 @@ var useRealtimeLineMetrics = ({
|
|
|
10330
10331
|
refreshMetrics: fetchData
|
|
10331
10332
|
}), [metrics2, lineDetails, loading, error, fetchData]);
|
|
10332
10333
|
};
|
|
10334
|
+
var useLines = () => {
|
|
10335
|
+
const supabase = useSupabase();
|
|
10336
|
+
const entityConfig = useEntityConfig();
|
|
10337
|
+
const [lines, setLines] = React24.useState([]);
|
|
10338
|
+
const [loading, setLoading] = React24.useState(true);
|
|
10339
|
+
const [error, setError] = React24.useState(null);
|
|
10340
|
+
const fetchLines = React24.useCallback(async () => {
|
|
10341
|
+
if (!supabase) {
|
|
10342
|
+
setLoading(false);
|
|
10343
|
+
return;
|
|
10344
|
+
}
|
|
10345
|
+
try {
|
|
10346
|
+
setLoading(true);
|
|
10347
|
+
let query = supabase.from("lines").select("*").eq("enable", true);
|
|
10348
|
+
if (entityConfig.companyId) {
|
|
10349
|
+
query = query.eq("company_id", entityConfig.companyId);
|
|
10350
|
+
}
|
|
10351
|
+
const { data, error: error2 } = await query;
|
|
10352
|
+
if (error2) throw error2;
|
|
10353
|
+
const sortedLines = (data || []).sort(
|
|
10354
|
+
(a, b) => (a.line_name || "").localeCompare(b.line_name || "")
|
|
10355
|
+
);
|
|
10356
|
+
setLines(sortedLines);
|
|
10357
|
+
} catch (err) {
|
|
10358
|
+
console.error("Error fetching lines:", err);
|
|
10359
|
+
setError(err);
|
|
10360
|
+
} finally {
|
|
10361
|
+
setLoading(false);
|
|
10362
|
+
}
|
|
10363
|
+
}, [supabase, entityConfig.companyId]);
|
|
10364
|
+
React24.useEffect(() => {
|
|
10365
|
+
fetchLines();
|
|
10366
|
+
}, [fetchLines]);
|
|
10367
|
+
return { lines, loading, error, refetch: fetchLines };
|
|
10368
|
+
};
|
|
10333
10369
|
var useTargets = (options) => {
|
|
10334
10370
|
const { companyId } = useEntityConfig();
|
|
10335
10371
|
const supabase = useSupabase();
|
|
@@ -12463,41 +12499,6 @@ function useClipTypesWithCounts(workspaceId, date, shiftId, totalOutput, options
|
|
|
12463
12499
|
counts
|
|
12464
12500
|
};
|
|
12465
12501
|
}
|
|
12466
|
-
var useLines = () => {
|
|
12467
|
-
const supabase = useSupabase();
|
|
12468
|
-
const entityConfig = useEntityConfig();
|
|
12469
|
-
const [lines, setLines] = React24.useState([]);
|
|
12470
|
-
const [loading, setLoading] = React24.useState(true);
|
|
12471
|
-
const [error, setError] = React24.useState(null);
|
|
12472
|
-
const fetchLines = React24.useCallback(async () => {
|
|
12473
|
-
if (!supabase) {
|
|
12474
|
-
setLoading(false);
|
|
12475
|
-
return;
|
|
12476
|
-
}
|
|
12477
|
-
try {
|
|
12478
|
-
setLoading(true);
|
|
12479
|
-
let query = supabase.from("lines").select("*").eq("enable", true);
|
|
12480
|
-
if (entityConfig.companyId) {
|
|
12481
|
-
query = query.eq("company_id", entityConfig.companyId);
|
|
12482
|
-
}
|
|
12483
|
-
const { data, error: error2 } = await query;
|
|
12484
|
-
if (error2) throw error2;
|
|
12485
|
-
const sortedLines = (data || []).sort(
|
|
12486
|
-
(a, b) => (a.line_name || "").localeCompare(b.line_name || "")
|
|
12487
|
-
);
|
|
12488
|
-
setLines(sortedLines);
|
|
12489
|
-
} catch (err) {
|
|
12490
|
-
console.error("Error fetching lines:", err);
|
|
12491
|
-
setError(err);
|
|
12492
|
-
} finally {
|
|
12493
|
-
setLoading(false);
|
|
12494
|
-
}
|
|
12495
|
-
}, [supabase, entityConfig.companyId]);
|
|
12496
|
-
React24.useEffect(() => {
|
|
12497
|
-
fetchLines();
|
|
12498
|
-
}, [fetchLines]);
|
|
12499
|
-
return { lines, loading, error, refetch: fetchLines };
|
|
12500
|
-
};
|
|
12501
12502
|
var MAX_RETRIES = 10;
|
|
12502
12503
|
var RETRY_DELAY = 500;
|
|
12503
12504
|
function useNavigation(customNavigate) {
|
|
@@ -25352,6 +25353,7 @@ var DEFAULT_BAR_RADIUS = [4, 4, 0, 0];
|
|
|
25352
25353
|
var BarChartComponent = ({
|
|
25353
25354
|
data,
|
|
25354
25355
|
bars,
|
|
25356
|
+
referenceLines,
|
|
25355
25357
|
xAxisDataKey = "name",
|
|
25356
25358
|
xAxisLabel,
|
|
25357
25359
|
yAxisLabel,
|
|
@@ -25435,6 +25437,22 @@ var BarChartComponent = ({
|
|
|
25435
25437
|
stroke: axisStrokeColor
|
|
25436
25438
|
}
|
|
25437
25439
|
),
|
|
25440
|
+
referenceLines?.map((line, idx) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
25441
|
+
recharts.ReferenceLine,
|
|
25442
|
+
{
|
|
25443
|
+
y: line.y,
|
|
25444
|
+
stroke: line.stroke || axisStrokeColor,
|
|
25445
|
+
strokeDasharray: line.strokeDasharray || "4 4",
|
|
25446
|
+
strokeWidth: line.strokeWidth,
|
|
25447
|
+
label: line.label ? {
|
|
25448
|
+
value: line.label,
|
|
25449
|
+
position: "insideTopRight",
|
|
25450
|
+
fill: line.stroke || axisTickFillColor,
|
|
25451
|
+
fontSize: 12
|
|
25452
|
+
} : void 0
|
|
25453
|
+
},
|
|
25454
|
+
`ref-line-${idx}`
|
|
25455
|
+
)),
|
|
25438
25456
|
showTooltip && /* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: tooltipFormatter || defaultTooltipFormatter, cursor: { fill: "transparent" } }),
|
|
25439
25457
|
showLegend && /* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { payload: legendPayload }),
|
|
25440
25458
|
bars.map((barConfig, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -25470,7 +25488,7 @@ var BarChartComponent = ({
|
|
|
25470
25488
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("w-full", className), children: chartContent });
|
|
25471
25489
|
};
|
|
25472
25490
|
var BarChart = React24__namespace.default.memo(BarChartComponent, (prevProps, nextProps) => {
|
|
25473
|
-
if (prevProps.xAxisDataKey !== nextProps.xAxisDataKey || prevProps.xAxisLabel !== nextProps.xAxisLabel || prevProps.yAxisLabel !== nextProps.yAxisLabel || prevProps.yAxisUnit !== nextProps.yAxisUnit || prevProps.layout !== nextProps.layout || prevProps.className !== nextProps.className || prevProps.showGrid !== nextProps.showGrid || prevProps.showLegend !== nextProps.showLegend || prevProps.showTooltip !== nextProps.showTooltip || prevProps.responsive !== nextProps.responsive || prevProps.aspect !== nextProps.aspect) {
|
|
25491
|
+
if (prevProps.xAxisDataKey !== nextProps.xAxisDataKey || prevProps.xAxisLabel !== nextProps.xAxisLabel || prevProps.yAxisLabel !== nextProps.yAxisLabel || prevProps.yAxisUnit !== nextProps.yAxisUnit || JSON.stringify(prevProps.referenceLines || []) !== JSON.stringify(nextProps.referenceLines || []) || prevProps.layout !== nextProps.layout || prevProps.className !== nextProps.className || prevProps.showGrid !== nextProps.showGrid || prevProps.showLegend !== nextProps.showLegend || prevProps.showTooltip !== nextProps.showTooltip || prevProps.responsive !== nextProps.responsive || prevProps.aspect !== nextProps.aspect) {
|
|
25474
25492
|
return false;
|
|
25475
25493
|
}
|
|
25476
25494
|
if (prevProps.data.length !== nextProps.data.length) {
|
|
@@ -28998,6 +29016,33 @@ var VideoControls = ({
|
|
|
28998
29016
|
}
|
|
28999
29017
|
);
|
|
29000
29018
|
};
|
|
29019
|
+
|
|
29020
|
+
// src/lib/utils/r2Detection.ts
|
|
29021
|
+
function isR2WorkerUrl(url, r2WorkerDomain) {
|
|
29022
|
+
if (!url || !r2WorkerDomain) return false;
|
|
29023
|
+
try {
|
|
29024
|
+
const workerDomain = new URL(r2WorkerDomain).hostname;
|
|
29025
|
+
return url.includes(workerDomain);
|
|
29026
|
+
} catch {
|
|
29027
|
+
return url.includes(r2WorkerDomain.replace(/https?:\/\//, ""));
|
|
29028
|
+
}
|
|
29029
|
+
}
|
|
29030
|
+
|
|
29031
|
+
// src/lib/services/hlsAuthService.ts
|
|
29032
|
+
async function getAuthTokenForHls(supabase) {
|
|
29033
|
+
try {
|
|
29034
|
+
const { data: { session } } = await supabase.auth.getSession();
|
|
29035
|
+
if (!session?.access_token) {
|
|
29036
|
+
console.warn("[HLS Auth] No active session, R2 streaming may fail");
|
|
29037
|
+
return null;
|
|
29038
|
+
}
|
|
29039
|
+
console.log("[HLS Auth] Retrieved token for HLS.js requests");
|
|
29040
|
+
return session.access_token;
|
|
29041
|
+
} catch (error) {
|
|
29042
|
+
console.error("[HLS Auth] Error getting auth token:", error);
|
|
29043
|
+
return null;
|
|
29044
|
+
}
|
|
29045
|
+
}
|
|
29001
29046
|
var ERROR_MAPPING = {
|
|
29002
29047
|
"networkError": {
|
|
29003
29048
|
code: 2,
|
|
@@ -29024,6 +29069,38 @@ var ERROR_MAPPING = {
|
|
|
29024
29069
|
canRetry: false
|
|
29025
29070
|
}
|
|
29026
29071
|
};
|
|
29072
|
+
var CLIP_ID_COMMENT_REGEX = /#\s*Clip ID:\s*([a-f0-9-]+)/i;
|
|
29073
|
+
var PLAYLIST_PROXY_REGEX = /\/api\/clips\/playlist\/([a-f0-9-]+)/i;
|
|
29074
|
+
var R2_FALLBACK_DETAILS = /* @__PURE__ */ new Set([
|
|
29075
|
+
"fragLoadError",
|
|
29076
|
+
"fragLoadHTTPError",
|
|
29077
|
+
"manifestLoadError",
|
|
29078
|
+
"levelLoadError",
|
|
29079
|
+
"keyLoadError"
|
|
29080
|
+
]);
|
|
29081
|
+
var extractClipIdFromSource = (source) => {
|
|
29082
|
+
if (!source) return null;
|
|
29083
|
+
const commentMatch = source.match(CLIP_ID_COMMENT_REGEX);
|
|
29084
|
+
if (commentMatch) return commentMatch[1];
|
|
29085
|
+
const urlMatch = source.match(PLAYLIST_PROXY_REGEX);
|
|
29086
|
+
if (urlMatch) return urlMatch[1];
|
|
29087
|
+
return null;
|
|
29088
|
+
};
|
|
29089
|
+
var ensureClipIdComment = (playlist, clipId) => {
|
|
29090
|
+
if (!playlist || playlist.includes(`# Clip ID: ${clipId}`) || CLIP_ID_COMMENT_REGEX.test(playlist)) {
|
|
29091
|
+
return playlist;
|
|
29092
|
+
}
|
|
29093
|
+
const lines = playlist.split("\n");
|
|
29094
|
+
if (lines.length === 0) return playlist;
|
|
29095
|
+
return [lines[0], `# Clip ID: ${clipId}`, ...lines.slice(1)].join("\n");
|
|
29096
|
+
};
|
|
29097
|
+
var getHlsErrorUrl = (data) => {
|
|
29098
|
+
return data?.frag?.url || data?.response?.url || data?.context?.url || data?.networkDetails?.response?.url || data?.url;
|
|
29099
|
+
};
|
|
29100
|
+
var getHlsErrorStatus = (data) => {
|
|
29101
|
+
const status = data?.response?.code ?? data?.response?.status ?? data?.networkDetails?.status ?? data?.networkDetails?.response?.status;
|
|
29102
|
+
return typeof status === "number" ? status : void 0;
|
|
29103
|
+
};
|
|
29027
29104
|
var hlsVideoPlayerStyles = `
|
|
29028
29105
|
.hls-video-player-container {
|
|
29029
29106
|
width: 100%;
|
|
@@ -29120,12 +29197,17 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29120
29197
|
onSeeked,
|
|
29121
29198
|
onClick
|
|
29122
29199
|
}, ref) => {
|
|
29200
|
+
const supabase = useSupabase();
|
|
29123
29201
|
const videoContainerRef = React24.useRef(null);
|
|
29124
29202
|
const videoRef = React24.useRef(null);
|
|
29125
29203
|
const hlsRef = React24.useRef(null);
|
|
29126
29204
|
const blobUrlRef = React24.useRef(null);
|
|
29205
|
+
const clipIdRef = React24.useRef(null);
|
|
29206
|
+
const r2FallbackAttemptedRef = React24.useRef(false);
|
|
29127
29207
|
const [isReady, setIsReady] = React24.useState(false);
|
|
29128
29208
|
const [isLoading, setIsLoading] = React24.useState(true);
|
|
29209
|
+
const [overrideSource, setOverrideSource] = React24.useState(null);
|
|
29210
|
+
const effectiveSrc = overrideSource && overrideSource.baseSrc === src ? overrideSource.value : src;
|
|
29129
29211
|
const [showControls, setShowControls] = React24.useState(true);
|
|
29130
29212
|
const [controlsPinned, setControlsPinned] = React24.useState(false);
|
|
29131
29213
|
const [isPlaying, setIsPlaying] = React24.useState(false);
|
|
@@ -29185,10 +29267,16 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29185
29267
|
onSeeked,
|
|
29186
29268
|
onLoadingChange
|
|
29187
29269
|
]);
|
|
29270
|
+
React24.useEffect(() => {
|
|
29271
|
+
clipIdRef.current = extractClipIdFromSource(src);
|
|
29272
|
+
r2FallbackAttemptedRef.current = false;
|
|
29273
|
+
setOverrideSource(null);
|
|
29274
|
+
}, [src]);
|
|
29188
29275
|
const stableHlsConfigRef = React24.useRef(hlsConfig);
|
|
29189
29276
|
const stableOptionsRef = React24.useRef(options);
|
|
29190
29277
|
const configSignatureRef = React24.useRef("");
|
|
29191
29278
|
const [configVersion, setConfigVersion] = React24.useState(0);
|
|
29279
|
+
const r2WorkerDomain = process.env.NEXT_PUBLIC_R2_WORKER_DOMAIN || "https://r2-stream-proxy.optifye-r2.workers.dev";
|
|
29192
29280
|
React24.useEffect(() => {
|
|
29193
29281
|
const serialized = JSON.stringify({
|
|
29194
29282
|
hlsConfig: hlsConfig || null,
|
|
@@ -29207,6 +29295,50 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29207
29295
|
setConfigVersion((prev) => prev + 1);
|
|
29208
29296
|
}
|
|
29209
29297
|
}, [hlsConfig, options]);
|
|
29298
|
+
const attemptS3Fallback = React24.useCallback(async (mode, reason, errorUrl) => {
|
|
29299
|
+
if (r2FallbackAttemptedRef.current) return false;
|
|
29300
|
+
const clipId = clipIdRef.current || extractClipIdFromSource(effectiveSrc);
|
|
29301
|
+
if (!clipId) {
|
|
29302
|
+
console.warn(`[HlsVideoPlayer] R2 fallback skipped - no clip ID (${reason})`);
|
|
29303
|
+
return false;
|
|
29304
|
+
}
|
|
29305
|
+
r2FallbackAttemptedRef.current = true;
|
|
29306
|
+
if (mode === "url") {
|
|
29307
|
+
const fallbackUrl = `/api/clips/playlist/${clipId}?source=s3`;
|
|
29308
|
+
console.warn(`[HlsVideoPlayer] Switching to S3 playlist URL (${reason})`, { errorUrl, clipId });
|
|
29309
|
+
setOverrideSource({ baseSrc: src, value: fallbackUrl });
|
|
29310
|
+
return true;
|
|
29311
|
+
}
|
|
29312
|
+
try {
|
|
29313
|
+
console.warn(`[HlsVideoPlayer] Fetching S3 playlist (${reason})`, { errorUrl, clipId });
|
|
29314
|
+
const response = await fetch(`/api/clips/playlist/${clipId}?source=s3`);
|
|
29315
|
+
if (!response.ok) {
|
|
29316
|
+
console.warn("[HlsVideoPlayer] S3 playlist fetch failed", { status: response.status, clipId });
|
|
29317
|
+
return false;
|
|
29318
|
+
}
|
|
29319
|
+
let playlistText = await response.text();
|
|
29320
|
+
playlistText = ensureClipIdComment(playlistText, clipId);
|
|
29321
|
+
setOverrideSource({ baseSrc: src, value: playlistText });
|
|
29322
|
+
return true;
|
|
29323
|
+
} catch (error) {
|
|
29324
|
+
console.error("[HlsVideoPlayer] S3 fallback failed", error);
|
|
29325
|
+
return false;
|
|
29326
|
+
}
|
|
29327
|
+
}, [effectiveSrc, src]);
|
|
29328
|
+
const maybeHandleR2Fallback = React24.useCallback((data) => {
|
|
29329
|
+
const errorUrl = getHlsErrorUrl(data);
|
|
29330
|
+
if (!errorUrl || !isR2WorkerUrl(errorUrl, r2WorkerDomain)) {
|
|
29331
|
+
return false;
|
|
29332
|
+
}
|
|
29333
|
+
const status = getHlsErrorStatus(data);
|
|
29334
|
+
const details = data?.details;
|
|
29335
|
+
const shouldFallback = R2_FALLBACK_DETAILS.has(details) || typeof status === "number" && status >= 400 && status < 500;
|
|
29336
|
+
if (!shouldFallback) {
|
|
29337
|
+
return false;
|
|
29338
|
+
}
|
|
29339
|
+
attemptS3Fallback("playlist", `HLS.js ${details || data?.type || "error"}`, errorUrl);
|
|
29340
|
+
return true;
|
|
29341
|
+
}, [attemptS3Fallback, r2WorkerDomain]);
|
|
29210
29342
|
const cleanupBlobUrl = React24.useCallback(() => {
|
|
29211
29343
|
if (blobUrlRef.current) {
|
|
29212
29344
|
URL.revokeObjectURL(blobUrlRef.current);
|
|
@@ -29247,36 +29379,56 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29247
29379
|
dispose: () => dispose()
|
|
29248
29380
|
};
|
|
29249
29381
|
}, [dispose]);
|
|
29250
|
-
const initializePlayer = React24.useCallback(() => {
|
|
29251
|
-
if (!videoRef.current || !
|
|
29382
|
+
const initializePlayer = React24.useCallback(async () => {
|
|
29383
|
+
if (!videoRef.current || !effectiveSrc) return;
|
|
29252
29384
|
const video = videoRef.current;
|
|
29253
29385
|
const player = playerLikeObject();
|
|
29386
|
+
let authToken = null;
|
|
29387
|
+
try {
|
|
29388
|
+
authToken = await getAuthTokenForHls(supabase);
|
|
29389
|
+
if (!authToken) {
|
|
29390
|
+
console.warn("[HLS Auth] No active session - R2 streaming may fail");
|
|
29391
|
+
}
|
|
29392
|
+
} catch (error) {
|
|
29393
|
+
console.error("[HLS Auth] Error getting auth token:", error);
|
|
29394
|
+
}
|
|
29254
29395
|
const mergedHlsConfig = {
|
|
29255
29396
|
...BASE_HLS_CONFIG,
|
|
29256
29397
|
...stableHlsConfigRef.current || {},
|
|
29257
|
-
...stableOptionsRef.current || {}
|
|
29398
|
+
...stableOptionsRef.current || {},
|
|
29399
|
+
// Modern HLS.js uses Fetch API - override fetch to add Authorization header for R2 Worker requests
|
|
29400
|
+
fetchSetup: function(context, initParams) {
|
|
29401
|
+
const url = context.url;
|
|
29402
|
+
if (isR2WorkerUrl(url, r2WorkerDomain) && authToken) {
|
|
29403
|
+
initParams.headers = {
|
|
29404
|
+
...initParams.headers,
|
|
29405
|
+
"Authorization": `Bearer ${authToken}`
|
|
29406
|
+
};
|
|
29407
|
+
}
|
|
29408
|
+
return new Request(url, initParams);
|
|
29409
|
+
}
|
|
29258
29410
|
};
|
|
29259
29411
|
cleanupBlobUrl();
|
|
29260
|
-
const isHLS =
|
|
29412
|
+
const isHLS = effectiveSrc.endsWith(".m3u8") || effectiveSrc.startsWith("#EXTM3U");
|
|
29261
29413
|
if (isHLS) {
|
|
29262
|
-
if (
|
|
29414
|
+
if (effectiveSrc.startsWith("#EXTM3U")) {
|
|
29263
29415
|
const safariMode = isSafari();
|
|
29264
29416
|
const browserName = getBrowserName();
|
|
29265
29417
|
if (safariMode) {
|
|
29266
|
-
const clipIdMatch =
|
|
29418
|
+
const clipIdMatch = effectiveSrc.match(/# Clip ID: ([a-f0-9-]+)/i);
|
|
29267
29419
|
if (clipIdMatch) {
|
|
29268
29420
|
const proxyUrl = `/api/clips/playlist/${clipIdMatch[1]}`;
|
|
29269
29421
|
console.log(`[HlsVideoPlayer] Safari detected (${browserName}) - using playlist proxy URL:`, proxyUrl);
|
|
29270
29422
|
video.src = proxyUrl;
|
|
29271
29423
|
} else {
|
|
29272
29424
|
console.warn("[HlsVideoPlayer] Safari detected but no clip ID found in playlist, trying blob URL fallback");
|
|
29273
|
-
const blob = new Blob([
|
|
29425
|
+
const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
|
|
29274
29426
|
const blobUrl = URL.createObjectURL(blob);
|
|
29275
29427
|
blobUrlRef.current = blobUrl;
|
|
29276
29428
|
video.src = blobUrl;
|
|
29277
29429
|
}
|
|
29278
29430
|
} else if (Hls3__default.default.isSupported()) {
|
|
29279
|
-
const blob = new Blob([
|
|
29431
|
+
const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
|
|
29280
29432
|
const blobUrl = URL.createObjectURL(blob);
|
|
29281
29433
|
blobUrlRef.current = blobUrl;
|
|
29282
29434
|
console.log(`[HlsVideoPlayer] Non-Safari browser (${browserName}) - using HLS.js with blob URL`);
|
|
@@ -29289,6 +29441,9 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29289
29441
|
});
|
|
29290
29442
|
hls.on(Hls3.Events.ERROR, (event, data) => {
|
|
29291
29443
|
console.error("[HlsVideoPlayer] HLS.js error:", data);
|
|
29444
|
+
if (maybeHandleR2Fallback(data)) {
|
|
29445
|
+
return;
|
|
29446
|
+
}
|
|
29292
29447
|
if (data.fatal) {
|
|
29293
29448
|
let errorInfo;
|
|
29294
29449
|
switch (data.type) {
|
|
@@ -29336,6 +29491,9 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29336
29491
|
});
|
|
29337
29492
|
hls.on(Hls3.Events.ERROR, (event, data) => {
|
|
29338
29493
|
console.error("[HlsVideoPlayer] HLS.js error:", data);
|
|
29494
|
+
if (maybeHandleR2Fallback(data)) {
|
|
29495
|
+
return;
|
|
29496
|
+
}
|
|
29339
29497
|
if (data.fatal) {
|
|
29340
29498
|
let errorInfo;
|
|
29341
29499
|
switch (data.type) {
|
|
@@ -29355,14 +29513,14 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29355
29513
|
eventCallbacksRef.current.onError?.(player, errorInfo);
|
|
29356
29514
|
}
|
|
29357
29515
|
});
|
|
29358
|
-
hls.loadSource(
|
|
29516
|
+
hls.loadSource(effectiveSrc);
|
|
29359
29517
|
hls.attachMedia(video);
|
|
29360
29518
|
} else {
|
|
29361
|
-
video.src =
|
|
29519
|
+
video.src = effectiveSrc;
|
|
29362
29520
|
}
|
|
29363
29521
|
}
|
|
29364
29522
|
} else {
|
|
29365
|
-
video.src =
|
|
29523
|
+
video.src = effectiveSrc;
|
|
29366
29524
|
}
|
|
29367
29525
|
const handleCanPlay = () => {
|
|
29368
29526
|
if (!hlsRef.current) {
|
|
@@ -29438,6 +29596,11 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29438
29596
|
eventCallbacksRef.current.onSeeked?.(player);
|
|
29439
29597
|
};
|
|
29440
29598
|
const handleError = () => {
|
|
29599
|
+
const currentSrc = video.currentSrc || video.src;
|
|
29600
|
+
if (isSafari() && currentSrc.includes("/api/clips/playlist/") && !currentSrc.includes("source=s3") && !r2FallbackAttemptedRef.current) {
|
|
29601
|
+
attemptS3Fallback("url", "native playback error", currentSrc);
|
|
29602
|
+
return;
|
|
29603
|
+
}
|
|
29441
29604
|
const error = video.error;
|
|
29442
29605
|
if (error) {
|
|
29443
29606
|
const errorInfo = {
|
|
@@ -29486,13 +29649,20 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29486
29649
|
video.removeEventListener("ratechange", handlePlaybackRateChange2);
|
|
29487
29650
|
};
|
|
29488
29651
|
}, [
|
|
29489
|
-
|
|
29652
|
+
effectiveSrc,
|
|
29490
29653
|
cleanupBlobUrl,
|
|
29491
29654
|
playerLikeObject,
|
|
29492
|
-
configVersion
|
|
29655
|
+
configVersion,
|
|
29656
|
+
supabase,
|
|
29657
|
+
attemptS3Fallback,
|
|
29658
|
+
maybeHandleR2Fallback,
|
|
29659
|
+
r2WorkerDomain
|
|
29493
29660
|
]);
|
|
29494
29661
|
React24.useEffect(() => {
|
|
29495
|
-
|
|
29662
|
+
let cleanup;
|
|
29663
|
+
initializePlayer().then((cleanupFn) => {
|
|
29664
|
+
cleanup = cleanupFn;
|
|
29665
|
+
});
|
|
29496
29666
|
return () => {
|
|
29497
29667
|
cleanup?.();
|
|
29498
29668
|
if (hlsRef.current) {
|
|
@@ -29502,7 +29672,7 @@ var HlsVideoPlayer = React24.forwardRef(({
|
|
|
29502
29672
|
cleanupBlobUrl();
|
|
29503
29673
|
setIsReady(false);
|
|
29504
29674
|
};
|
|
29505
|
-
}, [
|
|
29675
|
+
}, [effectiveSrc, initializePlayer, cleanupBlobUrl]);
|
|
29506
29676
|
React24.useEffect(() => {
|
|
29507
29677
|
if (videoRef.current) {
|
|
29508
29678
|
if (autoplay) {
|
|
@@ -31699,6 +31869,30 @@ var FileManagerFilters = ({
|
|
|
31699
31869
|
if (node.type === "category" || node.type === "percentile-category") {
|
|
31700
31870
|
toggleExpanded(node.id);
|
|
31701
31871
|
onFilterChange(node.id);
|
|
31872
|
+
if (node.id === "fast-cycles") {
|
|
31873
|
+
trackCoreEvent("Fast Clips Clicked", {
|
|
31874
|
+
workspaceId,
|
|
31875
|
+
date,
|
|
31876
|
+
shift,
|
|
31877
|
+
count: node.count || 0,
|
|
31878
|
+
percentile: filterState.percentile
|
|
31879
|
+
});
|
|
31880
|
+
} else if (node.id === "slow-cycles") {
|
|
31881
|
+
trackCoreEvent("Slow Clips Clicked", {
|
|
31882
|
+
workspaceId,
|
|
31883
|
+
date,
|
|
31884
|
+
shift,
|
|
31885
|
+
count: node.count || 0,
|
|
31886
|
+
percentile: filterState.percentile
|
|
31887
|
+
});
|
|
31888
|
+
} else if (node.id === "cycle_completion") {
|
|
31889
|
+
trackCoreEvent("Cycle Completions Clicked", {
|
|
31890
|
+
workspaceId,
|
|
31891
|
+
date,
|
|
31892
|
+
shift,
|
|
31893
|
+
count: node.count || 0
|
|
31894
|
+
});
|
|
31895
|
+
}
|
|
31702
31896
|
if (node.id !== "idle_time" && idleLabelFilter) {
|
|
31703
31897
|
setIdleLabelFilter(null);
|
|
31704
31898
|
}
|
|
@@ -36308,7 +36502,16 @@ var STATIC_COLORS = {
|
|
|
36308
36502
|
"Operator Idle": "#8b5cf6"
|
|
36309
36503
|
// violet-500 - Low Priority/Behavioral
|
|
36310
36504
|
};
|
|
36505
|
+
var PRODUCTIVE_COLOR = "#00AB45";
|
|
36506
|
+
var IDLE_COLOR = "#e5e7eb";
|
|
36311
36507
|
var getColorForEntry = (name, index) => {
|
|
36508
|
+
const normalized = name.trim().toLowerCase();
|
|
36509
|
+
if (normalized === "productive" || normalized === "productive time") {
|
|
36510
|
+
return PRODUCTIVE_COLOR;
|
|
36511
|
+
}
|
|
36512
|
+
if (normalized === "idle" || normalized === "idle time") {
|
|
36513
|
+
return IDLE_COLOR;
|
|
36514
|
+
}
|
|
36312
36515
|
if (STATIC_COLORS[name]) {
|
|
36313
36516
|
return STATIC_COLORS[name];
|
|
36314
36517
|
}
|
|
@@ -36452,6 +36655,7 @@ var IdleTimeReasonChart = ({
|
|
|
36452
36655
|
)
|
|
36453
36656
|
] });
|
|
36454
36657
|
};
|
|
36658
|
+
var IdleTimeReasonChart_default = IdleTimeReasonChart;
|
|
36455
36659
|
var DEFAULT_PERFORMANCE_DATA = {
|
|
36456
36660
|
avg_efficiency: 0,
|
|
36457
36661
|
underperforming_workspaces: 0,
|
|
@@ -37213,11 +37417,6 @@ var LinePdfGenerator = ({
|
|
|
37213
37417
|
doc.setLineWidth(0.8);
|
|
37214
37418
|
doc.line(20, 118, 190, 118);
|
|
37215
37419
|
const hourlyOverviewStartY = 123;
|
|
37216
|
-
doc.setFontSize(18);
|
|
37217
|
-
doc.setFont("helvetica", "bold");
|
|
37218
|
-
doc.setTextColor(40, 40, 40);
|
|
37219
|
-
doc.text("Hourly Output Overview", 20, 133);
|
|
37220
|
-
doc.setTextColor(0, 0, 0);
|
|
37221
37420
|
const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
|
|
37222
37421
|
const [hours, minutes] = startTimeStr.split(":");
|
|
37223
37422
|
const startHour = parseInt(hours);
|
|
@@ -37408,23 +37607,43 @@ var LinePdfGenerator = ({
|
|
|
37408
37607
|
return Math.round(lineInfo.metrics.current_output / shiftDuration);
|
|
37409
37608
|
});
|
|
37410
37609
|
}
|
|
37411
|
-
const tableHeaderY =
|
|
37412
|
-
const tableStartY =
|
|
37610
|
+
const tableHeaderY = 146;
|
|
37611
|
+
const tableStartY = 153;
|
|
37413
37612
|
const rowSpacing = 8;
|
|
37414
37613
|
const bottomPadding = 8;
|
|
37415
37614
|
const hourlyTableHeight = hourlyTimeRanges.length * rowSpacing;
|
|
37416
37615
|
const backgroundHeight = tableStartY - hourlyOverviewStartY + hourlyTableHeight + bottomPadding;
|
|
37417
37616
|
doc.setFillColor(245, 245, 245);
|
|
37418
37617
|
doc.roundedRect(15, hourlyOverviewStartY, 180, backgroundHeight, 3, 3, "F");
|
|
37618
|
+
doc.setFontSize(18);
|
|
37619
|
+
doc.setFont("helvetica", "bold");
|
|
37620
|
+
doc.setTextColor(40, 40, 40);
|
|
37621
|
+
doc.text("Hourly Performance", 20, 133);
|
|
37622
|
+
doc.setTextColor(0, 0, 0);
|
|
37623
|
+
const gridTopY = 139;
|
|
37624
|
+
const headerBottomY = 148;
|
|
37625
|
+
const colBoundaries = [20, 70, 100, 130, 155, 190];
|
|
37626
|
+
const totalRows = hourlyTimeRanges.length;
|
|
37627
|
+
const gridBottomY = headerBottomY + totalRows * rowSpacing;
|
|
37628
|
+
const tableHeight = gridBottomY - gridTopY;
|
|
37629
|
+
doc.setFillColor(255, 255, 255);
|
|
37630
|
+
doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "F");
|
|
37631
|
+
doc.setDrawColor(230, 230, 230);
|
|
37632
|
+
doc.setLineWidth(0.2);
|
|
37633
|
+
doc.roundedRect(20, gridTopY, 170, tableHeight, 2, 2, "S");
|
|
37634
|
+
doc.setDrawColor(200, 200, 200);
|
|
37635
|
+
doc.setLineWidth(0.3);
|
|
37636
|
+
doc.line(20, headerBottomY, 190, headerBottomY);
|
|
37637
|
+
colBoundaries.slice(1, -1).forEach((x) => {
|
|
37638
|
+
doc.line(x, gridTopY, x, gridBottomY);
|
|
37639
|
+
});
|
|
37419
37640
|
doc.setFontSize(11);
|
|
37420
37641
|
doc.setFont("helvetica", "bold");
|
|
37421
37642
|
doc.text("Time Range", 25, tableHeaderY);
|
|
37422
|
-
doc.text("Output",
|
|
37423
|
-
doc.text("Target",
|
|
37424
|
-
doc.text("Status",
|
|
37425
|
-
doc.
|
|
37426
|
-
doc.setDrawColor(200, 200, 200);
|
|
37427
|
-
doc.line(20, 146, 190, 146);
|
|
37643
|
+
doc.text("Output", 75, tableHeaderY);
|
|
37644
|
+
doc.text("Target", 105, tableHeaderY);
|
|
37645
|
+
doc.text("Status", 135, tableHeaderY);
|
|
37646
|
+
doc.text("Remarks", 160, tableHeaderY);
|
|
37428
37647
|
doc.setFont("helvetica", "normal");
|
|
37429
37648
|
let yPos = tableStartY;
|
|
37430
37649
|
const lineDateForTable = new Date(lineInfo.date);
|
|
@@ -37446,20 +37665,25 @@ var LinePdfGenerator = ({
|
|
|
37446
37665
|
const dataCollected = !isTodayForTable || hourNumber < currentHour;
|
|
37447
37666
|
const outputStr = dataCollected ? actualOutput.toString() : "TBD";
|
|
37448
37667
|
const targetStr = targetOutputPerHour.toString();
|
|
37668
|
+
if (index < totalRows - 1) {
|
|
37669
|
+
const rowBottomY = headerBottomY + (index + 1) * rowSpacing;
|
|
37670
|
+
doc.setDrawColor(200, 200, 200);
|
|
37671
|
+
doc.line(20, rowBottomY, 190, rowBottomY);
|
|
37672
|
+
}
|
|
37449
37673
|
doc.text(timeRange, 25, yPos);
|
|
37450
|
-
doc.text(outputStr,
|
|
37451
|
-
doc.text(targetStr,
|
|
37674
|
+
doc.text(outputStr, 75, yPos);
|
|
37675
|
+
doc.text(targetStr, 105, yPos);
|
|
37452
37676
|
if (!dataCollected) {
|
|
37453
37677
|
doc.setTextColor(100, 100, 100);
|
|
37454
|
-
doc.text("-",
|
|
37678
|
+
doc.text("-", 135, yPos);
|
|
37455
37679
|
} else if (actualOutput >= targetOutputPerHour) {
|
|
37456
37680
|
doc.setTextColor(0, 171, 69);
|
|
37457
37681
|
doc.setFont("ZapfDingbats", "normal");
|
|
37458
|
-
doc.text("4",
|
|
37682
|
+
doc.text("4", 135, yPos);
|
|
37459
37683
|
doc.setFont("helvetica", "normal");
|
|
37460
37684
|
} else {
|
|
37461
37685
|
doc.setTextColor(227, 67, 41);
|
|
37462
|
-
doc.text("\xD7",
|
|
37686
|
+
doc.text("\xD7", 135, yPos);
|
|
37463
37687
|
}
|
|
37464
37688
|
doc.setTextColor(0, 0, 0);
|
|
37465
37689
|
yPos += rowSpacing;
|
|
@@ -37467,7 +37691,7 @@ var LinePdfGenerator = ({
|
|
|
37467
37691
|
doc.addPage();
|
|
37468
37692
|
yPos = addHeaderPage2();
|
|
37469
37693
|
const workspaceSectionStartY = yPos;
|
|
37470
|
-
const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0,
|
|
37694
|
+
const sortedWorkspaces = [...workspaceData].sort((a, b) => (a.efficiency || 0) - (b.efficiency || 0)).slice(0, 3);
|
|
37471
37695
|
const wsRowCount = sortedWorkspaces.length > 0 ? sortedWorkspaces.length : 1;
|
|
37472
37696
|
const wsTableHeight = 10 + 8 + 7 + wsRowCount * 8 + 8;
|
|
37473
37697
|
doc.setFillColor(245, 245, 245);
|
|
@@ -37475,28 +37699,45 @@ var LinePdfGenerator = ({
|
|
|
37475
37699
|
doc.setFontSize(18);
|
|
37476
37700
|
doc.setFont("helvetica", "bold");
|
|
37477
37701
|
doc.setTextColor(40, 40, 40);
|
|
37478
|
-
doc.text("Poorest Performing Workspaces", 20, yPos);
|
|
37702
|
+
doc.text("Poorest Performing Workspaces", 20, yPos + 10);
|
|
37479
37703
|
doc.setTextColor(0, 0, 0);
|
|
37480
|
-
yPos +=
|
|
37704
|
+
yPos += 20;
|
|
37705
|
+
const wsGridTopY = yPos - 5;
|
|
37706
|
+
const wsHeaderBottomY = wsGridTopY + 8;
|
|
37707
|
+
const wsColBoundaries = [20, 80, 140, 190];
|
|
37708
|
+
const wsGridBottomY = wsHeaderBottomY + wsRowCount * 8;
|
|
37709
|
+
const wsTableTotalHeight = wsGridBottomY - wsGridTopY;
|
|
37710
|
+
doc.setFillColor(255, 255, 255);
|
|
37711
|
+
doc.roundedRect(20, wsGridTopY, 170, wsTableTotalHeight, 2, 2, "F");
|
|
37712
|
+
doc.setDrawColor(230, 230, 230);
|
|
37713
|
+
doc.setLineWidth(0.2);
|
|
37714
|
+
doc.roundedRect(20, wsGridTopY, 170, wsTableTotalHeight, 2, 2, "S");
|
|
37715
|
+
doc.setDrawColor(200, 200, 200);
|
|
37716
|
+
doc.setLineWidth(0.3);
|
|
37717
|
+
doc.line(20, wsHeaderBottomY, 190, wsHeaderBottomY);
|
|
37718
|
+
wsColBoundaries.slice(1, -1).forEach((x) => {
|
|
37719
|
+
doc.line(x, wsGridTopY, x, wsGridBottomY);
|
|
37720
|
+
});
|
|
37481
37721
|
doc.setFontSize(11);
|
|
37482
37722
|
doc.setFont("helvetica", "bold");
|
|
37483
|
-
yPos
|
|
37723
|
+
yPos = wsGridTopY + 5.5;
|
|
37484
37724
|
doc.text("Workspace", 25, yPos);
|
|
37485
37725
|
doc.text("Current/Target", 85, yPos);
|
|
37486
37726
|
doc.text("Efficiency", 145, yPos);
|
|
37487
|
-
yPos += 3;
|
|
37488
|
-
doc.setLineWidth(0.3);
|
|
37489
|
-
doc.setDrawColor(200, 200, 200);
|
|
37490
|
-
doc.line(20, yPos, 190, yPos);
|
|
37491
37727
|
doc.setFont("helvetica", "normal");
|
|
37492
|
-
yPos
|
|
37728
|
+
yPos = wsHeaderBottomY + 5.5;
|
|
37493
37729
|
if (sortedWorkspaces.length === 0) {
|
|
37494
37730
|
doc.text("No workspace data available", 25, yPos);
|
|
37495
|
-
yPos +=
|
|
37731
|
+
yPos += 8;
|
|
37496
37732
|
} else {
|
|
37497
37733
|
sortedWorkspaces.forEach((ws, index) => {
|
|
37498
37734
|
const workspaceName = getWorkspaceDisplayName(ws.workspace_name, lineInfo.line_id);
|
|
37499
37735
|
const truncatedName = workspaceName.length > 25 ? workspaceName.substring(0, 22) + "..." : workspaceName;
|
|
37736
|
+
if (index < wsRowCount - 1) {
|
|
37737
|
+
const rowBottomY = wsHeaderBottomY + (index + 1) * 8;
|
|
37738
|
+
doc.setDrawColor(200, 200, 200);
|
|
37739
|
+
doc.line(20, rowBottomY, 190, rowBottomY);
|
|
37740
|
+
}
|
|
37500
37741
|
doc.text(truncatedName, 25, yPos);
|
|
37501
37742
|
doc.text(`${ws.action_count || 0} / ${ws.action_threshold || 0}`, 85, yPos);
|
|
37502
37743
|
doc.text(`${(ws.efficiency || 0).toFixed(1)}%`, 145, yPos);
|
|
@@ -38839,18 +39080,27 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
|
|
|
38839
39080
|
const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
|
|
38840
39081
|
doc.text("Hourly Performance", 20, hourlyTitleY);
|
|
38841
39082
|
doc.setTextColor(0, 0, 0);
|
|
38842
|
-
doc.setFontSize(headerFontSize);
|
|
38843
|
-
doc.setFont("helvetica", "bold");
|
|
38844
39083
|
const baseHeaderY = hasIdleTimeReason ? 208 : 198;
|
|
38845
39084
|
const headerY = titleFontSize === 16 ? hasIdleTimeReason ? 206 : 196 : baseHeaderY;
|
|
38846
|
-
|
|
38847
|
-
|
|
38848
|
-
|
|
38849
|
-
|
|
38850
|
-
|
|
39085
|
+
const gridTopY = headerY - 5;
|
|
39086
|
+
const headerBottomY = gridTopY + 8;
|
|
39087
|
+
const colBoundaries = [20, 70, 100, 130, 155, 190];
|
|
39088
|
+
const totalRows = hourlyData.length;
|
|
39089
|
+
const gridBottomY = headerBottomY + totalRows * rowHeight;
|
|
38851
39090
|
doc.setDrawColor(200, 200, 200);
|
|
38852
|
-
|
|
38853
|
-
doc.line(20,
|
|
39091
|
+
doc.setLineWidth(0.3);
|
|
39092
|
+
doc.line(20, gridTopY, 190, gridTopY);
|
|
39093
|
+
doc.line(20, headerBottomY, 190, headerBottomY);
|
|
39094
|
+
colBoundaries.forEach((x) => {
|
|
39095
|
+
doc.line(x, gridTopY, x, gridBottomY);
|
|
39096
|
+
});
|
|
39097
|
+
doc.setFontSize(headerFontSize);
|
|
39098
|
+
doc.setFont("helvetica", "bold");
|
|
39099
|
+
doc.text("Time Range", 25, headerY);
|
|
39100
|
+
doc.text("Output", 75, headerY);
|
|
39101
|
+
doc.text("Target", 105, headerY);
|
|
39102
|
+
doc.text("Status", 135, headerY);
|
|
39103
|
+
doc.text("Remarks", 160, headerY);
|
|
38854
39104
|
doc.setFontSize(contentFontSize);
|
|
38855
39105
|
doc.setFont("helvetica", "normal");
|
|
38856
39106
|
let yPos = tableStartY;
|
|
@@ -38881,20 +39131,23 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
|
|
|
38881
39131
|
const dataCollected = !isToday2 || hourNumber < currentHour;
|
|
38882
39132
|
const outputStr = dataCollected ? output.toString() : "TBD";
|
|
38883
39133
|
const targetStr = hourlyTarget.toString();
|
|
39134
|
+
const rowBottomY = headerBottomY + (index + 1) * rowHeight;
|
|
39135
|
+
doc.setDrawColor(200, 200, 200);
|
|
39136
|
+
doc.line(20, rowBottomY, 190, rowBottomY);
|
|
38884
39137
|
doc.text(timeRange, 25, yPos);
|
|
38885
|
-
doc.text(outputStr,
|
|
38886
|
-
doc.text(targetStr,
|
|
39138
|
+
doc.text(outputStr, 75, yPos);
|
|
39139
|
+
doc.text(targetStr, 105, yPos);
|
|
38887
39140
|
if (!dataCollected) {
|
|
38888
39141
|
doc.setTextColor(100, 100, 100);
|
|
38889
|
-
doc.text("-",
|
|
39142
|
+
doc.text("-", 135, yPos);
|
|
38890
39143
|
} else if (output >= hourlyTarget) {
|
|
38891
39144
|
doc.setTextColor(0, 171, 69);
|
|
38892
39145
|
doc.setFont("ZapfDingbats", "normal");
|
|
38893
|
-
doc.text("4",
|
|
39146
|
+
doc.text("4", 135, yPos);
|
|
38894
39147
|
doc.setFont("helvetica", "normal");
|
|
38895
39148
|
} else {
|
|
38896
39149
|
doc.setTextColor(227, 67, 41);
|
|
38897
|
-
doc.text("\xD7",
|
|
39150
|
+
doc.text("\xD7", 135, yPos);
|
|
38898
39151
|
}
|
|
38899
39152
|
doc.setTextColor(0, 0, 0);
|
|
38900
39153
|
yPos += rowHeight;
|
|
@@ -39700,14 +39953,16 @@ var KPICard = ({
|
|
|
39700
39953
|
"uppercase tracking-wide sm:tracking-wider"
|
|
39701
39954
|
), children: title }),
|
|
39702
39955
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-0.5 sm:gap-2 flex-wrap", children: [
|
|
39703
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
39704
|
-
"
|
|
39705
|
-
|
|
39706
|
-
|
|
39707
|
-
|
|
39708
|
-
"
|
|
39709
|
-
|
|
39710
|
-
|
|
39956
|
+
isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-5 sm:h-6 md:h-7 w-12 sm:w-16 md:w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
39957
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
|
|
39958
|
+
"font-bold text-gray-900 dark:text-gray-50",
|
|
39959
|
+
"text-base sm:text-xl md:text-2xl"
|
|
39960
|
+
), children: formattedValue }),
|
|
39961
|
+
suffix && /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
|
|
39962
|
+
"font-medium text-gray-600 dark:text-gray-300",
|
|
39963
|
+
style.compact ? "text-base" : "text-lg"
|
|
39964
|
+
), children: suffix })
|
|
39965
|
+
] }),
|
|
39711
39966
|
!isLoading && trendInfo.shouldShowTrend && trendInfo.trendValue !== "neutral" && title !== "Output" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: trendInfo.colorClass, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
39712
39967
|
trendInfo.Icon,
|
|
39713
39968
|
{
|
|
@@ -39866,6 +40121,7 @@ var KPIHeader = ({
|
|
|
39866
40121
|
};
|
|
39867
40122
|
var KPISection = React24.memo(({
|
|
39868
40123
|
kpis,
|
|
40124
|
+
isLoading = false,
|
|
39869
40125
|
className,
|
|
39870
40126
|
layout: layout2 = "row",
|
|
39871
40127
|
gridColumns,
|
|
@@ -39874,7 +40130,8 @@ var KPISection = React24.memo(({
|
|
|
39874
40130
|
compactCards = false,
|
|
39875
40131
|
useSrcLayout = false
|
|
39876
40132
|
}) => {
|
|
39877
|
-
const
|
|
40133
|
+
const showSkeleton = isLoading || !kpis;
|
|
40134
|
+
const outputDifference = kpis ? kpis.outputProgress.current - kpis.outputProgress.idealOutput : 0;
|
|
39878
40135
|
const outputIsOnTarget = outputDifference >= 0;
|
|
39879
40136
|
if (useSrcLayout) {
|
|
39880
40137
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -39891,27 +40148,30 @@ var KPISection = React24.memo(({
|
|
|
39891
40148
|
KPICard,
|
|
39892
40149
|
{
|
|
39893
40150
|
title: "Underperforming",
|
|
39894
|
-
value: "2/3",
|
|
39895
|
-
change: 0
|
|
40151
|
+
value: showSkeleton ? "" : "2/3",
|
|
40152
|
+
change: 0,
|
|
40153
|
+
isLoading: showSkeleton
|
|
39896
40154
|
}
|
|
39897
40155
|
) }),
|
|
39898
40156
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
39899
40157
|
KPICard,
|
|
39900
40158
|
{
|
|
39901
40159
|
title: "Efficiency",
|
|
39902
|
-
value: kpis.efficiency.value,
|
|
39903
|
-
change: kpis.efficiency.change,
|
|
39904
|
-
suffix: "%"
|
|
40160
|
+
value: showSkeleton ? "" : kpis.efficiency.value,
|
|
40161
|
+
change: showSkeleton ? 0 : kpis.efficiency.change,
|
|
40162
|
+
suffix: "%",
|
|
40163
|
+
isLoading: showSkeleton
|
|
39905
40164
|
}
|
|
39906
40165
|
) }),
|
|
39907
40166
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
39908
40167
|
KPICard,
|
|
39909
40168
|
{
|
|
39910
40169
|
title: "Output Progress",
|
|
39911
|
-
value: `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
|
|
39912
|
-
change: kpis.outputProgress.change,
|
|
40170
|
+
value: showSkeleton ? "" : `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
|
|
40171
|
+
change: showSkeleton ? 0 : kpis.outputProgress.change,
|
|
39913
40172
|
outputDifference,
|
|
39914
|
-
showOutputDetails:
|
|
40173
|
+
showOutputDetails: !showSkeleton,
|
|
40174
|
+
isLoading: showSkeleton
|
|
39915
40175
|
}
|
|
39916
40176
|
) })
|
|
39917
40177
|
]
|
|
@@ -39922,34 +40182,30 @@ var KPISection = React24.memo(({
|
|
|
39922
40182
|
{
|
|
39923
40183
|
key: "underperforming",
|
|
39924
40184
|
title: "Underperforming",
|
|
39925
|
-
value: `${kpis.underperformingWorkers.current}/${kpis.underperformingWorkers.total}`,
|
|
39926
|
-
change: kpis.underperformingWorkers.change
|
|
40185
|
+
value: showSkeleton ? "" : `${kpis.underperformingWorkers.current}/${kpis.underperformingWorkers.total}`,
|
|
40186
|
+
change: showSkeleton ? 0 : kpis.underperformingWorkers.change,
|
|
40187
|
+
suffix: void 0,
|
|
40188
|
+
status: void 0
|
|
39927
40189
|
},
|
|
39928
40190
|
{
|
|
39929
40191
|
key: "efficiency",
|
|
39930
40192
|
title: "Efficiency",
|
|
39931
|
-
value: kpis.efficiency.value,
|
|
39932
|
-
change: kpis.efficiency.change,
|
|
39933
|
-
suffix: "%"
|
|
40193
|
+
value: showSkeleton ? "" : kpis.efficiency.value,
|
|
40194
|
+
change: showSkeleton ? 0 : kpis.efficiency.change,
|
|
40195
|
+
suffix: "%",
|
|
40196
|
+
status: void 0
|
|
39934
40197
|
},
|
|
39935
40198
|
{
|
|
39936
40199
|
key: "outputProgress",
|
|
39937
40200
|
title: "Output Progress",
|
|
39938
|
-
value: `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
|
|
39939
|
-
change: kpis.outputProgress.change,
|
|
39940
|
-
|
|
40201
|
+
value: showSkeleton ? "" : `${kpis.outputProgress.current}/${kpis.outputProgress.target}`,
|
|
40202
|
+
change: showSkeleton ? 0 : kpis.outputProgress.change,
|
|
40203
|
+
suffix: void 0,
|
|
40204
|
+
status: showSkeleton ? void 0 : {
|
|
39941
40205
|
tooltipText: outputIsOnTarget ? "On Target" : "Off Target",
|
|
39942
40206
|
positive: outputIsOnTarget
|
|
39943
40207
|
}
|
|
39944
40208
|
}
|
|
39945
|
-
// Only include these additional KPIs if not using the src layout
|
|
39946
|
-
// ...(kpis.qualityCompliance ? [{
|
|
39947
|
-
// key: 'qualityCompliance',
|
|
39948
|
-
// title: 'Quality Compliance',
|
|
39949
|
-
// value: kpis.qualityCompliance.value,
|
|
39950
|
-
// change: kpis.qualityCompliance.change,
|
|
39951
|
-
// suffix: '%',
|
|
39952
|
-
// }] : []),
|
|
39953
40209
|
];
|
|
39954
40210
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
39955
40211
|
KPIGrid,
|
|
@@ -39966,7 +40222,8 @@ var KPISection = React24.memo(({
|
|
|
39966
40222
|
change: kpi.change,
|
|
39967
40223
|
suffix: kpi.suffix,
|
|
39968
40224
|
status: kpi.status,
|
|
39969
|
-
style: { variant: cardVariant, compact: compactCards }
|
|
40225
|
+
style: { variant: cardVariant, compact: compactCards },
|
|
40226
|
+
isLoading: showSkeleton
|
|
39970
40227
|
},
|
|
39971
40228
|
kpi.key
|
|
39972
40229
|
))
|
|
@@ -39975,6 +40232,9 @@ var KPISection = React24.memo(({
|
|
|
39975
40232
|
}, (prevProps, nextProps) => {
|
|
39976
40233
|
const prevKpis = prevProps.kpis;
|
|
39977
40234
|
const nextKpis = nextProps.kpis;
|
|
40235
|
+
if (prevProps.isLoading !== nextProps.isLoading) return false;
|
|
40236
|
+
if (!prevKpis && !nextKpis) return true;
|
|
40237
|
+
if (!prevKpis || !nextKpis) return false;
|
|
39978
40238
|
if (prevKpis === nextKpis) return true;
|
|
39979
40239
|
if (Math.abs(prevKpis.efficiency.value - nextKpis.efficiency.value) >= 0.5) return false;
|
|
39980
40240
|
if (prevKpis.underperformingWorkers.current !== nextKpis.underperformingWorkers.current || prevKpis.underperformingWorkers.total !== nextKpis.underperformingWorkers.total) return false;
|
|
@@ -41313,6 +41573,17 @@ var SideNavBar = React24.memo(({
|
|
|
41313
41573
|
});
|
|
41314
41574
|
onMobileMenuClose?.();
|
|
41315
41575
|
}, [navigate, onMobileMenuClose]);
|
|
41576
|
+
const handleImprovementClick = React24.useCallback(() => {
|
|
41577
|
+
navigate("/improvement-center", {
|
|
41578
|
+
trackingEvent: {
|
|
41579
|
+
name: "Improvement Center Clicked",
|
|
41580
|
+
properties: {
|
|
41581
|
+
source: "side_nav"
|
|
41582
|
+
}
|
|
41583
|
+
}
|
|
41584
|
+
});
|
|
41585
|
+
onMobileMenuClose?.();
|
|
41586
|
+
}, [navigate, onMobileMenuClose]);
|
|
41316
41587
|
const handleTargetsClick = React24.useCallback(() => {
|
|
41317
41588
|
navigate("/targets", {
|
|
41318
41589
|
trackingEvent: {
|
|
@@ -41430,6 +41701,7 @@ var SideNavBar = React24.memo(({
|
|
|
41430
41701
|
const homeButtonClasses = React24.useMemo(() => getButtonClasses("/"), [getButtonClasses, pathname]);
|
|
41431
41702
|
const leaderboardButtonClasses = React24.useMemo(() => getButtonClasses("/leaderboard"), [getButtonClasses, pathname]);
|
|
41432
41703
|
const kpisButtonClasses = React24.useMemo(() => getButtonClasses("/kpis"), [getButtonClasses, pathname]);
|
|
41704
|
+
const improvementButtonClasses = React24.useMemo(() => getButtonClasses("/improvement-center"), [getButtonClasses, pathname]);
|
|
41433
41705
|
const targetsButtonClasses = React24.useMemo(() => getButtonClasses("/targets"), [getButtonClasses, pathname]);
|
|
41434
41706
|
const shiftsButtonClasses = React24.useMemo(() => getButtonClasses("/shifts"), [getButtonClasses, pathname]);
|
|
41435
41707
|
const teamManagementButtonClasses = React24.useMemo(() => getButtonClasses("/team-management"), [getButtonClasses, pathname]);
|
|
@@ -41497,6 +41769,22 @@ var SideNavBar = React24.memo(({
|
|
|
41497
41769
|
]
|
|
41498
41770
|
}
|
|
41499
41771
|
),
|
|
41772
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
41773
|
+
"button",
|
|
41774
|
+
{
|
|
41775
|
+
onClick: handleImprovementClick,
|
|
41776
|
+
className: improvementButtonClasses,
|
|
41777
|
+
"aria-label": "Improvement Center",
|
|
41778
|
+
tabIndex: 0,
|
|
41779
|
+
role: "tab",
|
|
41780
|
+
"aria-selected": pathname === "/improvement-center" || pathname.startsWith("/improvement-center/"),
|
|
41781
|
+
children: [
|
|
41782
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.LightBulbIcon, { className: "w-5 h-5 mb-1" }),
|
|
41783
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-[10px] font-medium leading-tight text-center", children: "Improvement" }),
|
|
41784
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px] font-semibold text-orange-500 uppercase tracking-wide bg-orange-50 px-1.5 py-0.5 rounded", children: "Beta" })
|
|
41785
|
+
]
|
|
41786
|
+
}
|
|
41787
|
+
),
|
|
41500
41788
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
41501
41789
|
"button",
|
|
41502
41790
|
{
|
|
@@ -41697,6 +41985,19 @@ var SideNavBar = React24.memo(({
|
|
|
41697
41985
|
]
|
|
41698
41986
|
}
|
|
41699
41987
|
),
|
|
41988
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
41989
|
+
"button",
|
|
41990
|
+
{
|
|
41991
|
+
onClick: handleMobileNavClick(handleImprovementClick),
|
|
41992
|
+
className: getMobileButtonClass("/improvement-center"),
|
|
41993
|
+
"aria-label": "Improvement Center",
|
|
41994
|
+
children: [
|
|
41995
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.LightBulbIcon, { className: getIconClass("/improvement-center") }),
|
|
41996
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-medium", children: "Improvement" }),
|
|
41997
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2 text-[10px] font-semibold text-orange-500 uppercase tracking-wide bg-orange-50 px-1.5 py-0.5 rounded", children: "Beta" })
|
|
41998
|
+
]
|
|
41999
|
+
}
|
|
42000
|
+
),
|
|
41700
42001
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
41701
42002
|
"button",
|
|
41702
42003
|
{
|
|
@@ -45727,7 +46028,8 @@ var TeamUsagePdfGenerator = ({
|
|
|
45727
46028
|
usageData,
|
|
45728
46029
|
daysInRange = 1,
|
|
45729
46030
|
title = "Team Usage Report",
|
|
45730
|
-
className
|
|
46031
|
+
className,
|
|
46032
|
+
iconOnly = false
|
|
45731
46033
|
}) => {
|
|
45732
46034
|
const [isGenerating, setIsGenerating] = React24.useState(false);
|
|
45733
46035
|
const generatePDF = async () => {
|
|
@@ -45907,10 +46209,11 @@ var TeamUsagePdfGenerator = ({
|
|
|
45907
46209
|
{
|
|
45908
46210
|
onClick: generatePDF,
|
|
45909
46211
|
disabled: isGenerating || !usageData,
|
|
45910
|
-
className: `inline-flex items-center gap-2 rounded-lg bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed ${className || ""}`,
|
|
46212
|
+
className: `inline-flex items-center gap-2 rounded-lg bg-gray-100 ${iconOnly ? "p-2" : "px-4 py-2"} text-sm font-medium text-gray-700 hover:bg-gray-200 active:bg-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${className || ""}`,
|
|
46213
|
+
"aria-label": iconOnly ? isGenerating ? "Generating PDF..." : "Export PDF" : void 0,
|
|
45911
46214
|
children: [
|
|
45912
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" }),
|
|
45913
|
-
isGenerating ? "Generating..." : "Export PDF"
|
|
46215
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: iconOnly ? "h-5 w-5" : "h-4 w-4" }),
|
|
46216
|
+
!iconOnly && (isGenerating ? "Generating..." : "Export PDF")
|
|
45914
46217
|
]
|
|
45915
46218
|
}
|
|
45916
46219
|
);
|
|
@@ -48679,10 +48982,19 @@ function HomeView({
|
|
|
48679
48982
|
const [errorMessage, setErrorMessage] = React24.useState(null);
|
|
48680
48983
|
const [displayNamesInitialized, setDisplayNamesInitialized] = React24.useState(false);
|
|
48681
48984
|
const [hasInitialDataLoaded, setHasInitialDataLoaded] = React24.useState(false);
|
|
48985
|
+
const KPI_CACHE_KEY = "optifye_kpi_cache";
|
|
48682
48986
|
const dashboardConfig = useDashboardConfig();
|
|
48683
48987
|
const entityConfig = useEntityConfig();
|
|
48684
48988
|
const supabaseClient = useSupabaseClient();
|
|
48685
48989
|
const { user } = useAuth();
|
|
48990
|
+
const { lines: dbLines } = useLines();
|
|
48991
|
+
const mergedLineNames = React24.useMemo(() => {
|
|
48992
|
+
const merged = { ...lineNames };
|
|
48993
|
+
dbLines.forEach((line) => {
|
|
48994
|
+
merged[line.id] = line.line_name;
|
|
48995
|
+
});
|
|
48996
|
+
return merged;
|
|
48997
|
+
}, [lineNames, dbLines]);
|
|
48686
48998
|
const isSupervisor = user?.role_level === "supervisor";
|
|
48687
48999
|
const hasMultipleLines = allLineIds.length > 1;
|
|
48688
49000
|
const availableLineIds = React24.useMemo(() => {
|
|
@@ -48789,6 +49101,42 @@ function HomeView({
|
|
|
48789
49101
|
}
|
|
48790
49102
|
return buildKPIsFromLineMetricsRow(row);
|
|
48791
49103
|
}, [selectedLineId, factoryViewId, lineMetrics, metricsLoading]);
|
|
49104
|
+
const [cachedKpis, setCachedKpis] = React24.useState(() => {
|
|
49105
|
+
if (typeof window === "undefined") return null;
|
|
49106
|
+
try {
|
|
49107
|
+
const cached = localStorage.getItem(KPI_CACHE_KEY);
|
|
49108
|
+
if (cached) {
|
|
49109
|
+
const parsed = JSON.parse(cached);
|
|
49110
|
+
return parsed[selectedLineId] || parsed[factoryViewId] || null;
|
|
49111
|
+
}
|
|
49112
|
+
} catch (e) {
|
|
49113
|
+
}
|
|
49114
|
+
return null;
|
|
49115
|
+
});
|
|
49116
|
+
React24.useEffect(() => {
|
|
49117
|
+
if (kpis && typeof window !== "undefined") {
|
|
49118
|
+
try {
|
|
49119
|
+
const existing = localStorage.getItem(KPI_CACHE_KEY);
|
|
49120
|
+
const cache = existing ? JSON.parse(existing) : {};
|
|
49121
|
+
cache[selectedLineId] = kpis;
|
|
49122
|
+
localStorage.setItem(KPI_CACHE_KEY, JSON.stringify(cache));
|
|
49123
|
+
setCachedKpis(kpis);
|
|
49124
|
+
} catch (e) {
|
|
49125
|
+
}
|
|
49126
|
+
}
|
|
49127
|
+
}, [kpis, selectedLineId, KPI_CACHE_KEY]);
|
|
49128
|
+
React24.useEffect(() => {
|
|
49129
|
+
if (typeof window === "undefined") return;
|
|
49130
|
+
try {
|
|
49131
|
+
const cached = localStorage.getItem(KPI_CACHE_KEY);
|
|
49132
|
+
if (cached) {
|
|
49133
|
+
const parsed = JSON.parse(cached);
|
|
49134
|
+
setCachedKpis(parsed[selectedLineId] || null);
|
|
49135
|
+
}
|
|
49136
|
+
} catch (e) {
|
|
49137
|
+
}
|
|
49138
|
+
}, [selectedLineId, KPI_CACHE_KEY]);
|
|
49139
|
+
const displayKpis = kpis || cachedKpis;
|
|
48792
49140
|
const {
|
|
48793
49141
|
activeBreaks: allActiveBreaks,
|
|
48794
49142
|
isLoading: breaksLoading,
|
|
@@ -49090,16 +49438,16 @@ function HomeView({
|
|
|
49090
49438
|
// Use stable string representation instead of spreading array
|
|
49091
49439
|
JSON.stringify(workspaceMetrics.map((w) => `${w.workspace_uuid}-${Math.round(w.efficiency)}-${w.trend}`))
|
|
49092
49440
|
]);
|
|
49093
|
-
const memoizedKPIs = React24.useMemo(() =>
|
|
49441
|
+
const memoizedKPIs = React24.useMemo(() => displayKpis, [
|
|
49094
49442
|
// Only update reference when values change by at least 1%
|
|
49095
|
-
|
|
49096
|
-
|
|
49097
|
-
|
|
49098
|
-
|
|
49099
|
-
|
|
49100
|
-
|
|
49443
|
+
displayKpis?.efficiency?.value ? Math.round(displayKpis.efficiency.value) : null,
|
|
49444
|
+
displayKpis?.underperformingWorkers?.current,
|
|
49445
|
+
displayKpis?.underperformingWorkers?.total,
|
|
49446
|
+
displayKpis?.outputProgress?.current,
|
|
49447
|
+
displayKpis?.outputProgress?.target,
|
|
49448
|
+
displayKpis?.avgCycleTime?.value ? Math.round(displayKpis.avgCycleTime.value * 10) / 10 : null,
|
|
49101
49449
|
// Round to 1 decimal
|
|
49102
|
-
|
|
49450
|
+
displayKpis?.qualityCompliance?.value ? Math.round(displayKpis.qualityCompliance.value) : null
|
|
49103
49451
|
]);
|
|
49104
49452
|
React24.useEffect(() => {
|
|
49105
49453
|
setIsHydrated(true);
|
|
@@ -49117,7 +49465,7 @@ function HomeView({
|
|
|
49117
49465
|
trackCoreEvent("Home Line Filter Changed", {
|
|
49118
49466
|
previous_line_id: selectedLineId,
|
|
49119
49467
|
new_line_id: value,
|
|
49120
|
-
line_name:
|
|
49468
|
+
line_name: mergedLineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
|
|
49121
49469
|
});
|
|
49122
49470
|
try {
|
|
49123
49471
|
sessionStorage.setItem(LINE_FILTER_STORAGE_KEY, value);
|
|
@@ -49151,9 +49499,9 @@ function HomeView({
|
|
|
49151
49499
|
}
|
|
49152
49500
|
return /* @__PURE__ */ jsxRuntime.jsxs(Select, { onValueChange: handleLineChange, defaultValue: selectedLineId, children: [
|
|
49153
49501
|
/* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-full sm:w-[200px] bg-white border border-gray-200 shadow-sm rounded-md h-9 sm:h-9 text-xs sm:text-sm px-2 sm:px-3", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Select a line" }) }),
|
|
49154
|
-
/* @__PURE__ */ jsxRuntime.jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: id3, children:
|
|
49502
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: id3, children: mergedLineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
|
|
49155
49503
|
] });
|
|
49156
|
-
}, [availableLineIds, handleLineChange, selectedLineId,
|
|
49504
|
+
}, [availableLineIds, handleLineChange, selectedLineId, mergedLineNames, factoryViewId, allLineIds.length]);
|
|
49157
49505
|
const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
|
|
49158
49506
|
const isDataLoading = metricsLoading;
|
|
49159
49507
|
if (isInitialLoading) {
|
|
@@ -49182,7 +49530,7 @@ function HomeView({
|
|
|
49182
49530
|
lineTitle,
|
|
49183
49531
|
lineId: selectedLineId === factoryViewId ? allLineIds[0] : selectedLineId,
|
|
49184
49532
|
className: "w-full",
|
|
49185
|
-
headerControls:
|
|
49533
|
+
headerControls: /* @__PURE__ */ jsxRuntime.jsx(KPISection2, { kpis: memoizedKPIs, isLoading: !memoizedKPIs, className: "w-full sm:w-auto" })
|
|
49186
49534
|
}
|
|
49187
49535
|
) }) }),
|
|
49188
49536
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto sm:overflow-hidden relative", children: [
|
|
@@ -56471,35 +56819,62 @@ var TeamManagementView = ({
|
|
|
56471
56819
|
) }) });
|
|
56472
56820
|
}
|
|
56473
56821
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("min-h-screen bg-slate-50", className), children: [
|
|
56474
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsxRuntime.
|
|
56475
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "
|
|
56476
|
-
|
|
56477
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
56478
|
-
/* @__PURE__ */ jsxRuntime.
|
|
56479
|
-
|
|
56480
|
-
|
|
56481
|
-
|
|
56482
|
-
|
|
56483
|
-
|
|
56484
|
-
|
|
56485
|
-
|
|
56486
|
-
|
|
56487
|
-
|
|
56488
|
-
|
|
56489
|
-
|
|
56490
|
-
|
|
56491
|
-
|
|
56492
|
-
|
|
56493
|
-
|
|
56494
|
-
|
|
56495
|
-
|
|
56496
|
-
|
|
56497
|
-
|
|
56498
|
-
|
|
56499
|
-
|
|
56500
|
-
|
|
56822
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 sm:px-6 lg:px-8 py-3 sm:py-4", children: [
|
|
56823
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
56824
|
+
/* @__PURE__ */ jsxRuntime.jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }),
|
|
56825
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-gray-900 truncate px-2", children: pageTitle }) }),
|
|
56826
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
56827
|
+
canViewUsageStats && /* @__PURE__ */ jsxRuntime.jsx(
|
|
56828
|
+
TeamUsagePdfGenerator,
|
|
56829
|
+
{
|
|
56830
|
+
users,
|
|
56831
|
+
usageData,
|
|
56832
|
+
daysInRange: usageDateRange.daysElapsed,
|
|
56833
|
+
title: "Team Usage Report",
|
|
56834
|
+
iconOnly: true
|
|
56835
|
+
}
|
|
56836
|
+
),
|
|
56837
|
+
canAddUsers && /* @__PURE__ */ jsxRuntime.jsx(
|
|
56838
|
+
"button",
|
|
56839
|
+
{
|
|
56840
|
+
onClick: () => setIsAddUserDialogOpen(true),
|
|
56841
|
+
className: "p-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 active:bg-blue-800 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
|
|
56842
|
+
"aria-label": "Add User",
|
|
56843
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { className: "w-5 h-5" })
|
|
56844
|
+
}
|
|
56845
|
+
)
|
|
56846
|
+
] })
|
|
56847
|
+
] }) }),
|
|
56848
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "hidden sm:flex items-center justify-between", children: [
|
|
56849
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(BackButtonMinimal, { onClick: handleBack, text: "Back" }) }),
|
|
56850
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
|
|
56851
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: pageTitle }),
|
|
56852
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: pageDescription })
|
|
56853
|
+
] }),
|
|
56854
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
56855
|
+
canViewUsageStats && /* @__PURE__ */ jsxRuntime.jsx(
|
|
56856
|
+
TeamUsagePdfGenerator,
|
|
56857
|
+
{
|
|
56858
|
+
users,
|
|
56859
|
+
usageData,
|
|
56860
|
+
daysInRange: usageDateRange.daysElapsed,
|
|
56861
|
+
title: "Team Usage Report"
|
|
56862
|
+
}
|
|
56863
|
+
),
|
|
56864
|
+
canAddUsers && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
56865
|
+
"button",
|
|
56866
|
+
{
|
|
56867
|
+
onClick: () => setIsAddUserDialogOpen(true),
|
|
56868
|
+
className: "inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
|
|
56869
|
+
children: [
|
|
56870
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserPlus, { className: "w-4 h-4" }),
|
|
56871
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Add User" })
|
|
56872
|
+
]
|
|
56873
|
+
}
|
|
56874
|
+
)
|
|
56875
|
+
] })
|
|
56501
56876
|
] })
|
|
56502
|
-
] }) })
|
|
56877
|
+
] }) }),
|
|
56503
56878
|
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "px-4 sm:px-6 lg:px-8 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
56504
56879
|
UserManagementTable,
|
|
56505
56880
|
{
|
|
@@ -57437,135 +57812,258 @@ Please ensure:
|
|
|
57437
57812
|
}
|
|
57438
57813
|
var AuthenticatedTicketsView = withAuth(React24__namespace.default.memo(TicketsView));
|
|
57439
57814
|
var TicketsView_default = TicketsView;
|
|
57440
|
-
|
|
57441
|
-
|
|
57442
|
-
|
|
57443
|
-
|
|
57444
|
-
|
|
57445
|
-
|
|
57446
|
-
|
|
57447
|
-
|
|
57448
|
-
evidence: {
|
|
57449
|
-
type: "video",
|
|
57450
|
-
label: "Operator Cycle Recording",
|
|
57451
|
-
videoUrls: [
|
|
57452
|
-
"/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4",
|
|
57453
|
-
"/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4",
|
|
57454
|
-
// Mock duplicate for demo
|
|
57455
|
-
"/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4"
|
|
57456
|
-
// Mock duplicate for demo
|
|
57457
|
-
]
|
|
57458
|
-
},
|
|
57459
|
-
impact: "Potential 50% efficiency gain or standard adjustment needed.",
|
|
57460
|
-
date: "Today",
|
|
57461
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
57462
|
-
},
|
|
57463
|
-
{
|
|
57464
|
-
id: "rec-2",
|
|
57465
|
-
type: "efficiency",
|
|
57466
|
-
title: "Consistent Low Efficiency Period",
|
|
57467
|
-
location: "Line 2",
|
|
57468
|
-
line: "Line 2",
|
|
57469
|
-
description: "Efficiency from 7pm - 8pm is consistently lower than standard. This pattern has been observed for the last 3 days.",
|
|
57470
|
-
evidence: {
|
|
57471
|
-
type: "chart",
|
|
57472
|
-
label: "7pm - 8pm Efficiency Trend",
|
|
57473
|
-
chartData: [
|
|
57474
|
-
{ label: "7:00", value: 45, standard: 85 },
|
|
57475
|
-
{ label: "7:15", value: 42, standard: 85 },
|
|
57476
|
-
{ label: "7:30", value: 40, standard: 85 },
|
|
57477
|
-
{ label: "7:45", value: 48, standard: 85 },
|
|
57478
|
-
{ label: "8:00", value: 50, standard: 85 }
|
|
57479
|
-
]
|
|
57480
|
-
},
|
|
57481
|
-
impact: "Loss of approx. 40 units per hour during shift changeover.",
|
|
57482
|
-
date: "Today",
|
|
57483
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
57484
|
-
},
|
|
57485
|
-
{
|
|
57486
|
-
id: "rec-3",
|
|
57487
|
-
type: "downtime",
|
|
57488
|
-
title: "Recurring Micro-stoppages",
|
|
57489
|
-
location: "Unit 3: Conveyor",
|
|
57490
|
-
line: "Line 1",
|
|
57491
|
-
description: "Frequent micro-stoppages detected every 15 minutes. This suggests a potential jam or sensor issue.",
|
|
57492
|
-
evidence: {
|
|
57493
|
-
type: "chart",
|
|
57494
|
-
label: "Downtime Events",
|
|
57495
|
-
chartData: [
|
|
57496
|
-
{ label: "9:00", value: 2, standard: 0 },
|
|
57497
|
-
{ label: "9:15", value: 3, standard: 0 },
|
|
57498
|
-
{ label: "9:30", value: 1, standard: 0 },
|
|
57499
|
-
{ label: "9:45", value: 4, standard: 0 },
|
|
57500
|
-
{ label: "10:00", value: 2, standard: 0 }
|
|
57501
|
-
]
|
|
57502
|
-
},
|
|
57503
|
-
impact: "Cumulative downtime of 45 mins per shift.",
|
|
57504
|
-
date: "Yesterday",
|
|
57505
|
-
timestamp: new Date(Date.now() - 864e5).toISOString()
|
|
57506
|
-
},
|
|
57507
|
-
{
|
|
57508
|
-
id: "rec-4",
|
|
57509
|
-
type: "efficiency",
|
|
57510
|
-
title: "High Idle Time Detected",
|
|
57511
|
-
location: "Packaging Station",
|
|
57512
|
-
line: "Line 2",
|
|
57513
|
-
description: "Operator idle time exceeds 20% of shift duration. Consider rebalancing workload.",
|
|
57514
|
-
evidence: {
|
|
57515
|
-
type: "video",
|
|
57516
|
-
label: "Idle Time Observation",
|
|
57517
|
-
videoUrls: [
|
|
57518
|
-
"/faa70d7a-d687-40c7-b01f-e04a3bb5a425_cam1_cycle_completion_20250708_091413_a54ee206.mp4"
|
|
57519
|
-
]
|
|
57520
|
-
},
|
|
57521
|
-
impact: "Potential to reassign 1 FTE to other tasks.",
|
|
57522
|
-
date: "Yesterday",
|
|
57523
|
-
timestamp: new Date(Date.now() - 864e5).toISOString()
|
|
57524
|
-
},
|
|
57525
|
-
{
|
|
57526
|
-
id: "rec-5",
|
|
57527
|
-
type: "cycle_time",
|
|
57528
|
-
title: "Slow Setup Time",
|
|
57529
|
-
location: "Machine 5",
|
|
57530
|
-
line: "Line 1",
|
|
57531
|
-
description: "Setup time for product changeover took 45 mins, standard is 20 mins.",
|
|
57532
|
-
evidence: {
|
|
57533
|
-
type: "chart",
|
|
57534
|
-
label: "Changeover Duration",
|
|
57535
|
-
chartData: [
|
|
57536
|
-
{ label: "Prev 1", value: 25, standard: 20 },
|
|
57537
|
-
{ label: "Prev 2", value: 22, standard: 20 },
|
|
57538
|
-
{ label: "Current", value: 45, standard: 20 },
|
|
57539
|
-
{ label: "Avg", value: 30, standard: 20 },
|
|
57540
|
-
{ label: "Target", value: 20, standard: 20 }
|
|
57541
|
-
]
|
|
57542
|
-
},
|
|
57543
|
-
impact: "25 mins of lost production time.",
|
|
57544
|
-
date: "2 Days Ago",
|
|
57545
|
-
timestamp: new Date(Date.now() - 1728e5).toISOString()
|
|
57815
|
+
|
|
57816
|
+
// src/lib/api/s3-clips-parser.ts
|
|
57817
|
+
function parseS3Uri(s3Uri, sopCategories) {
|
|
57818
|
+
const path = new URL(s3Uri).pathname;
|
|
57819
|
+
const parts = path.split("/").filter((p) => p);
|
|
57820
|
+
if (s3Uri.includes("missed_qchecks")) {
|
|
57821
|
+
console.warn(`Skipping missed_qchecks URI in parseS3Uri: ${s3Uri}`);
|
|
57822
|
+
return null;
|
|
57546
57823
|
}
|
|
57824
|
+
if (parts.length < 8) {
|
|
57825
|
+
console.warn(`Invalid S3 path structure: ${s3Uri} - Too few parts: ${parts.length}, expected at least 8`);
|
|
57826
|
+
return null;
|
|
57827
|
+
}
|
|
57828
|
+
try {
|
|
57829
|
+
const datePart = parts[2];
|
|
57830
|
+
const shiftPart = parts[3];
|
|
57831
|
+
const violationType = parts[4];
|
|
57832
|
+
let folderName = "";
|
|
57833
|
+
let timestamp = "";
|
|
57834
|
+
for (let i = 5; i < parts.length; i++) {
|
|
57835
|
+
const part = parts[i];
|
|
57836
|
+
if (part && part.includes("_") && /\d{8}_\d{6}/.test(part)) {
|
|
57837
|
+
folderName = part;
|
|
57838
|
+
const timeMatch = folderName.match(/_(\d{8})_(\d{6})_/);
|
|
57839
|
+
if (timeMatch) {
|
|
57840
|
+
timestamp = `${timeMatch[2].substring(0, 2)}:${timeMatch[2].substring(2, 4)}:${timeMatch[2].substring(4, 6)}`;
|
|
57841
|
+
break;
|
|
57842
|
+
}
|
|
57843
|
+
}
|
|
57844
|
+
}
|
|
57845
|
+
if (!timestamp) {
|
|
57846
|
+
console.warn(`Couldn't extract timestamp from any part: ${parts.join("/")}`);
|
|
57847
|
+
timestamp = "00:00:00";
|
|
57848
|
+
}
|
|
57849
|
+
let severity = "low";
|
|
57850
|
+
let type = "bottleneck";
|
|
57851
|
+
let description = "Analysis Clip";
|
|
57852
|
+
const normalizedViolationType = violationType.toLowerCase().trim();
|
|
57853
|
+
if (sopCategories && sopCategories.length > 0) {
|
|
57854
|
+
const matchedCategory = sopCategories.find((category) => {
|
|
57855
|
+
const categoryId = category.id.toLowerCase();
|
|
57856
|
+
const s3FolderName = (category.s3FolderName || category.id).toLowerCase();
|
|
57857
|
+
return categoryId === normalizedViolationType || s3FolderName === normalizedViolationType || // Also check for partial matches for flexibility
|
|
57858
|
+
normalizedViolationType.includes(categoryId) || normalizedViolationType.includes(s3FolderName);
|
|
57859
|
+
});
|
|
57860
|
+
if (matchedCategory) {
|
|
57861
|
+
type = matchedCategory.id;
|
|
57862
|
+
description = matchedCategory.description || matchedCategory.label;
|
|
57863
|
+
if (matchedCategory.color.includes("red")) {
|
|
57864
|
+
severity = "high";
|
|
57865
|
+
} else if (matchedCategory.color.includes("yellow") || matchedCategory.color.includes("orange")) {
|
|
57866
|
+
severity = "medium";
|
|
57867
|
+
} else {
|
|
57868
|
+
severity = "low";
|
|
57869
|
+
}
|
|
57870
|
+
console.log(`Matched SOP category: ${matchedCategory.id} for violation type: ${violationType}`);
|
|
57871
|
+
return { timestamp, severity, description, type, originalUri: s3Uri };
|
|
57872
|
+
}
|
|
57873
|
+
}
|
|
57874
|
+
switch (normalizedViolationType) {
|
|
57875
|
+
case "idle_time":
|
|
57876
|
+
case "idle":
|
|
57877
|
+
case "low_value":
|
|
57878
|
+
case "low value":
|
|
57879
|
+
case "low_value_moment":
|
|
57880
|
+
case "low_value_moments":
|
|
57881
|
+
case "low value moment":
|
|
57882
|
+
case "low value moments":
|
|
57883
|
+
type = "low_value";
|
|
57884
|
+
severity = "low";
|
|
57885
|
+
description = "Idle Time Detected";
|
|
57886
|
+
break;
|
|
57887
|
+
case "sop_deviation":
|
|
57888
|
+
type = "missing_quality_check";
|
|
57889
|
+
severity = "high";
|
|
57890
|
+
description = "SOP Deviations";
|
|
57891
|
+
break;
|
|
57892
|
+
case "long_cycle_time":
|
|
57893
|
+
severity = "high";
|
|
57894
|
+
type = "long_cycle_time";
|
|
57895
|
+
description = "Long Cycle Time Detected";
|
|
57896
|
+
break;
|
|
57897
|
+
case "best_cycle_time":
|
|
57898
|
+
type = "best_cycle_time";
|
|
57899
|
+
severity = "low";
|
|
57900
|
+
description = "Best Cycle Time Performance";
|
|
57901
|
+
break;
|
|
57902
|
+
case "worst_cycle_time":
|
|
57903
|
+
type = "worst_cycle_time";
|
|
57904
|
+
severity = "high";
|
|
57905
|
+
description = "Worst Cycle Time Performance";
|
|
57906
|
+
break;
|
|
57907
|
+
case "cycle_completion":
|
|
57908
|
+
case "completed_cycles":
|
|
57909
|
+
case "completed_cycle":
|
|
57910
|
+
type = "cycle_completion";
|
|
57911
|
+
severity = "low";
|
|
57912
|
+
description = "Cycle Completion";
|
|
57913
|
+
break;
|
|
57914
|
+
case "running_cycle":
|
|
57915
|
+
case "active_cycle":
|
|
57916
|
+
case "production_cycle":
|
|
57917
|
+
type = "running_cycle";
|
|
57918
|
+
severity = "low";
|
|
57919
|
+
description = "Active Production Cycle";
|
|
57920
|
+
break;
|
|
57921
|
+
case "setup_state":
|
|
57922
|
+
case "machine_setup":
|
|
57923
|
+
case "line_setup":
|
|
57924
|
+
type = "setup_state";
|
|
57925
|
+
severity = "medium";
|
|
57926
|
+
description = "Machine Setup Activity";
|
|
57927
|
+
break;
|
|
57928
|
+
case "medium_bottleneck":
|
|
57929
|
+
severity = "medium";
|
|
57930
|
+
description = "Medium Bottleneck Identified";
|
|
57931
|
+
break;
|
|
57932
|
+
case "minor_bottleneck":
|
|
57933
|
+
case "mild_bottleneck":
|
|
57934
|
+
severity = "low";
|
|
57935
|
+
description = "Minor Bottleneck Identified";
|
|
57936
|
+
break;
|
|
57937
|
+
default:
|
|
57938
|
+
if (normalizedViolationType.includes("sop") && normalizedViolationType.includes("deviation")) {
|
|
57939
|
+
type = "missing_quality_check";
|
|
57940
|
+
severity = "high";
|
|
57941
|
+
description = "SOP Deviations";
|
|
57942
|
+
} else if (normalizedViolationType.includes("worst") && normalizedViolationType.includes("cycle")) {
|
|
57943
|
+
type = "worst_cycle_time";
|
|
57944
|
+
severity = "high";
|
|
57945
|
+
description = "Worst Cycle Time Performance";
|
|
57946
|
+
} else if (normalizedViolationType.includes("best") && normalizedViolationType.includes("cycle")) {
|
|
57947
|
+
type = "best_cycle_time";
|
|
57948
|
+
severity = "low";
|
|
57949
|
+
description = "Best Cycle Time Performance";
|
|
57950
|
+
} else if (normalizedViolationType.includes("long") && normalizedViolationType.includes("cycle")) {
|
|
57951
|
+
type = "long_cycle_time";
|
|
57952
|
+
severity = "high";
|
|
57953
|
+
description = "Long Cycle Time Detected";
|
|
57954
|
+
} else if (normalizedViolationType.includes("cycle") && (normalizedViolationType.includes("completion") || normalizedViolationType.includes("complete"))) {
|
|
57955
|
+
type = "cycle_completion";
|
|
57956
|
+
severity = "low";
|
|
57957
|
+
description = "Cycle Completion";
|
|
57958
|
+
} else if (normalizedViolationType.includes("running") && normalizedViolationType.includes("cycle")) {
|
|
57959
|
+
type = "running_cycle";
|
|
57960
|
+
severity = "low";
|
|
57961
|
+
description = "Active Production Cycle";
|
|
57962
|
+
} else if (normalizedViolationType.includes("setup") || normalizedViolationType.includes("machine") && normalizedViolationType.includes("setup")) {
|
|
57963
|
+
type = "setup_state";
|
|
57964
|
+
severity = "medium";
|
|
57965
|
+
description = "Machine Setup Activity";
|
|
57966
|
+
} else {
|
|
57967
|
+
description = `Clip type: ${violationType.replace(/_/g, " ")}`;
|
|
57968
|
+
console.log(`Detected unknown violation type: ${violationType} in URI: ${s3Uri}`);
|
|
57969
|
+
}
|
|
57970
|
+
break;
|
|
57971
|
+
}
|
|
57972
|
+
return { timestamp, severity, description, type, originalUri: s3Uri };
|
|
57973
|
+
} catch (error) {
|
|
57974
|
+
console.error(`Error parsing S3 URI: ${s3Uri}`, error);
|
|
57975
|
+
return null;
|
|
57976
|
+
}
|
|
57977
|
+
}
|
|
57978
|
+
function shuffleArray(array) {
|
|
57979
|
+
const shuffled = [...array];
|
|
57980
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
57981
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
57982
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
57983
|
+
}
|
|
57984
|
+
return shuffled;
|
|
57985
|
+
}
|
|
57986
|
+
var CATEGORY_OPTIONS = [
|
|
57987
|
+
{ id: "all", label: "All" },
|
|
57988
|
+
{ id: "cycle_time", label: "Cycle Time" },
|
|
57989
|
+
{ id: "efficiency", label: "Efficiency" },
|
|
57990
|
+
{ id: "downtime", label: "Downtime" }
|
|
57547
57991
|
];
|
|
57548
|
-
var
|
|
57992
|
+
var ClipVideoCarousel = ({ clips, clipsService }) => {
|
|
57549
57993
|
const [currentIndex, setCurrentIndex] = React24.useState(0);
|
|
57994
|
+
const [videos, setVideos] = React24.useState([]);
|
|
57995
|
+
const [loading, setLoading] = React24.useState(false);
|
|
57996
|
+
const [error, setError] = React24.useState(null);
|
|
57997
|
+
React24.useEffect(() => {
|
|
57998
|
+
let cancelled = false;
|
|
57999
|
+
const load = async () => {
|
|
58000
|
+
if (!clipsService) {
|
|
58001
|
+
setError("Clips are not configured for this dashboard");
|
|
58002
|
+
return;
|
|
58003
|
+
}
|
|
58004
|
+
if (!clips || clips.length === 0) return;
|
|
58005
|
+
setLoading(true);
|
|
58006
|
+
setError(null);
|
|
58007
|
+
setVideos([]);
|
|
58008
|
+
try {
|
|
58009
|
+
const resolved = await Promise.all(
|
|
58010
|
+
clips.map(async (c) => {
|
|
58011
|
+
const video = await clipsService.getClipById(c.clip_id);
|
|
58012
|
+
if (!video) return null;
|
|
58013
|
+
const cycleTime = typeof c.cycle_time_seconds === "number" ? c.cycle_time_seconds : typeof c.cycle_sec === "number" ? c.cycle_sec : void 0;
|
|
58014
|
+
return {
|
|
58015
|
+
...video,
|
|
58016
|
+
cycle_time_seconds: cycleTime ?? video.cycle_time_seconds
|
|
58017
|
+
};
|
|
58018
|
+
})
|
|
58019
|
+
);
|
|
58020
|
+
if (cancelled) return;
|
|
58021
|
+
setVideos(resolved.filter(Boolean) || []);
|
|
58022
|
+
} catch (err) {
|
|
58023
|
+
if (cancelled) return;
|
|
58024
|
+
setError(err?.message || "Failed to load clips");
|
|
58025
|
+
setVideos([]);
|
|
58026
|
+
} finally {
|
|
58027
|
+
if (!cancelled) setLoading(false);
|
|
58028
|
+
}
|
|
58029
|
+
};
|
|
58030
|
+
load();
|
|
58031
|
+
return () => {
|
|
58032
|
+
cancelled = true;
|
|
58033
|
+
};
|
|
58034
|
+
}, [clipsService, clips]);
|
|
58035
|
+
React24.useEffect(() => {
|
|
58036
|
+
if (videos.length > 0 && currentIndex >= videos.length) {
|
|
58037
|
+
setCurrentIndex(0);
|
|
58038
|
+
}
|
|
58039
|
+
}, [videos.length, currentIndex]);
|
|
57550
58040
|
const nextVideo = () => {
|
|
57551
|
-
setCurrentIndex((prev) => (prev + 1) %
|
|
58041
|
+
setCurrentIndex((prev) => (prev + 1) % Math.max(videos.length, 1));
|
|
57552
58042
|
};
|
|
57553
58043
|
const prevVideo = () => {
|
|
57554
|
-
setCurrentIndex((prev) => (prev - 1 +
|
|
58044
|
+
setCurrentIndex((prev) => (prev - 1 + Math.max(videos.length, 1)) % Math.max(videos.length, 1));
|
|
57555
58045
|
};
|
|
57556
|
-
if (!
|
|
58046
|
+
if (!clips || clips.length === 0) return null;
|
|
58047
|
+
const currentVideo = videos[currentIndex];
|
|
57557
58048
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group bg-gray-900 rounded-lg overflow-hidden aspect-video", children: [
|
|
57558
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
57559
|
-
|
|
58049
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-white/80", children: "Loading clips\u2026" }),
|
|
58050
|
+
!loading && error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-white/80", children: error }),
|
|
58051
|
+
!loading && !error && currentVideo?.src && /* @__PURE__ */ jsxRuntime.jsx(
|
|
58052
|
+
VideoPlayer,
|
|
57560
58053
|
{
|
|
57561
|
-
src:
|
|
57562
|
-
className: "w-full h-full object-cover opacity-
|
|
57563
|
-
controls: true
|
|
58054
|
+
src: currentVideo.src,
|
|
58055
|
+
className: "w-full h-full object-cover opacity-90 group-hover:opacity-100 transition-opacity",
|
|
58056
|
+
controls: true,
|
|
58057
|
+
playsInline: true
|
|
57564
58058
|
},
|
|
57565
|
-
|
|
58059
|
+
currentVideo.src
|
|
57566
58060
|
),
|
|
57567
|
-
/* @__PURE__ */ jsxRuntime.
|
|
57568
|
-
|
|
58061
|
+
!loading && !error && typeof currentVideo?.cycle_time_seconds === "number" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-3 left-3 bg-black/60 text-white text-xs px-2 py-1 rounded-full font-medium pointer-events-none", children: [
|
|
58062
|
+
currentVideo.cycle_time_seconds.toFixed(1),
|
|
58063
|
+
"s"
|
|
58064
|
+
] }),
|
|
58065
|
+
!loading && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none group-hover:opacity-0 transition-opacity", children: /* @__PURE__ */ jsxRuntime.jsx(outline.PlayCircleIcon, { className: "w-16 h-16 text-white opacity-80" }) }),
|
|
58066
|
+
videos.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
57569
58067
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
57570
58068
|
"button",
|
|
57571
58069
|
{
|
|
@@ -57593,70 +58091,273 @@ var VideoCarousel = ({ urls }) => {
|
|
|
57593
58091
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-3 right-3 bg-black/60 text-white text-xs px-2 py-1 rounded-full font-medium pointer-events-none", children: [
|
|
57594
58092
|
currentIndex + 1,
|
|
57595
58093
|
" / ",
|
|
57596
|
-
|
|
58094
|
+
videos.length
|
|
57597
58095
|
] })
|
|
57598
58096
|
] })
|
|
57599
58097
|
] });
|
|
57600
58098
|
};
|
|
57601
|
-
var
|
|
57602
|
-
const
|
|
57603
|
-
|
|
57604
|
-
|
|
57605
|
-
|
|
57606
|
-
|
|
58099
|
+
var BarChartEvidence = ({ x, y, unit, data, series, referenceLines }) => {
|
|
58100
|
+
const scale2 = unit === "fraction" ? 100 : 1;
|
|
58101
|
+
const yAxisUnit = unit === "fraction" ? "%" : "";
|
|
58102
|
+
const targetLineIndex = referenceLines?.findIndex(
|
|
58103
|
+
(line) => (line.label || "").toLowerCase().includes("target")
|
|
58104
|
+
) ?? -1;
|
|
58105
|
+
const resolvedTargetLineIndex = targetLineIndex >= 0 ? targetLineIndex : referenceLines?.length === 1 ? 0 : -1;
|
|
58106
|
+
const unitLabel = unit === "fraction" ? "%" : unit === "pieces" ? "PPH" : unit === "ratio" ? "" : unit || "";
|
|
58107
|
+
const formatTargetValue = (value) => {
|
|
58108
|
+
if (unit === "fraction") return value.toFixed(0);
|
|
58109
|
+
if (Number.isInteger(value)) return value.toString();
|
|
58110
|
+
return value.toFixed(1);
|
|
58111
|
+
};
|
|
58112
|
+
const formatTargetLabel = (label, value) => {
|
|
58113
|
+
const base = label || "Target";
|
|
58114
|
+
const suffix = unitLabel ? unitLabel === "%" ? "%" : ` ${unitLabel}` : "";
|
|
58115
|
+
return `${base}: ${formatTargetValue(value)}${suffix}`;
|
|
58116
|
+
};
|
|
58117
|
+
const isMultiSeries = Array.isArray(series) && series.length > 0 && Array.isArray(data);
|
|
58118
|
+
const chartData = isMultiSeries ? (data || []).map((row) => {
|
|
58119
|
+
const scaled = { ...row };
|
|
58120
|
+
series?.forEach((s) => {
|
|
58121
|
+
const raw = row[s.dataKey];
|
|
58122
|
+
if (typeof raw === "number") {
|
|
58123
|
+
scaled[s.dataKey] = raw * scale2;
|
|
58124
|
+
}
|
|
58125
|
+
});
|
|
58126
|
+
return scaled;
|
|
58127
|
+
}) : (x || []).map((xv, idx) => {
|
|
58128
|
+
const yv = y?.[idx];
|
|
58129
|
+
if (yv === null || yv === void 0) return null;
|
|
58130
|
+
const label = typeof xv === "number" ? `${xv.toString().padStart(2, "0")}:00` : String(xv);
|
|
58131
|
+
return { name: label, value: yv * scale2 };
|
|
58132
|
+
}).filter(Boolean);
|
|
58133
|
+
const referenceLineConfigs = (referenceLines || []).map((line, idx) => {
|
|
58134
|
+
const scaledValue = line.y * scale2;
|
|
58135
|
+
const isTarget = idx === resolvedTargetLineIndex;
|
|
58136
|
+
return {
|
|
58137
|
+
...line,
|
|
58138
|
+
y: scaledValue,
|
|
58139
|
+
label: isTarget ? formatTargetLabel(line.label, scaledValue) : line.label,
|
|
58140
|
+
stroke: isTarget ? "#E34329" : line.stroke,
|
|
58141
|
+
strokeDasharray: isTarget ? "5 5" : line.strokeDasharray,
|
|
58142
|
+
strokeWidth: isTarget ? 2 : line.strokeWidth
|
|
58143
|
+
};
|
|
58144
|
+
});
|
|
58145
|
+
const targetLine = resolvedTargetLineIndex >= 0 ? (referenceLines || [])[resolvedTargetLineIndex] : void 0;
|
|
58146
|
+
const targetValue = targetLine ? targetLine.y * scale2 : null;
|
|
58147
|
+
const targetLabel = targetLine && targetValue !== null ? formatTargetLabel(targetLine.label, targetValue) : null;
|
|
58148
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-48 w-full bg-white border border-gray-100 rounded-lg p-2 relative", children: [
|
|
58149
|
+
targetLabel && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-2 right-2 z-10 pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 rounded-md border border-gray-200 bg-white/90 px-2 py-1 text-[11px] text-gray-700 shadow-sm", children: [
|
|
58150
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block w-6 border-t-2 border-dashed", style: { borderColor: "#E34329" } }),
|
|
58151
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold", children: targetLabel })
|
|
58152
|
+
] }) }),
|
|
58153
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
58154
|
+
BarChart,
|
|
58155
|
+
{
|
|
58156
|
+
data: chartData,
|
|
58157
|
+
bars: isMultiSeries ? (series || []).map((s) => ({
|
|
58158
|
+
dataKey: s.dataKey,
|
|
58159
|
+
name: s.name,
|
|
58160
|
+
fill: s.fill,
|
|
58161
|
+
stackId: s.stackId,
|
|
58162
|
+
labelList: false
|
|
58163
|
+
})) : [
|
|
58164
|
+
{
|
|
58165
|
+
dataKey: "value",
|
|
58166
|
+
name: "Value",
|
|
58167
|
+
fill: "#f87171",
|
|
58168
|
+
// red-400
|
|
58169
|
+
labelList: false
|
|
58170
|
+
}
|
|
58171
|
+
],
|
|
58172
|
+
xAxisDataKey: "name",
|
|
58173
|
+
showLegend: isMultiSeries && (series || []).length > 1,
|
|
58174
|
+
showGrid: true,
|
|
58175
|
+
aspect: 2.5,
|
|
58176
|
+
yAxisUnit,
|
|
58177
|
+
referenceLines: referenceLineConfigs,
|
|
58178
|
+
className: "h-full"
|
|
58179
|
+
}
|
|
58180
|
+
)
|
|
58181
|
+
] });
|
|
58182
|
+
};
|
|
58183
|
+
var PieChartEvidence = ({ data }) => {
|
|
58184
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-48 w-full bg-white border border-gray-100 rounded-lg p-2", children: /* @__PURE__ */ jsxRuntime.jsx(IdleTimeReasonChart_default, { data }) });
|
|
58185
|
+
};
|
|
58186
|
+
var LineChartEvidence = ({ x, y, unit }) => {
|
|
58187
|
+
const scale2 = unit === "fraction" ? 100 : 1;
|
|
58188
|
+
const yAxisUnit = unit === "fraction" ? "%" : "";
|
|
58189
|
+
const chartData = x.map((xv, idx) => {
|
|
58190
|
+
const yv = y[idx];
|
|
58191
|
+
if (yv === null || yv === void 0) return null;
|
|
58192
|
+
return { name: String(xv), value: yv * scale2 };
|
|
58193
|
+
}).filter(Boolean);
|
|
57607
58194
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-48 w-full bg-white border border-gray-100 rounded-lg p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
57608
|
-
|
|
58195
|
+
LineChart,
|
|
57609
58196
|
{
|
|
57610
58197
|
data: chartData,
|
|
57611
|
-
|
|
58198
|
+
lines: [
|
|
57612
58199
|
{
|
|
57613
58200
|
dataKey: "value",
|
|
57614
|
-
name: "
|
|
57615
|
-
|
|
57616
|
-
|
|
57617
|
-
|
|
58201
|
+
name: "Value",
|
|
58202
|
+
stroke: "#f87171",
|
|
58203
|
+
strokeWidth: 2,
|
|
58204
|
+
dot: true
|
|
57618
58205
|
}
|
|
57619
58206
|
],
|
|
57620
58207
|
xAxisDataKey: "name",
|
|
57621
58208
|
showLegend: false,
|
|
57622
58209
|
showGrid: true,
|
|
57623
58210
|
aspect: 2.5,
|
|
58211
|
+
yAxisUnit,
|
|
57624
58212
|
className: "h-full"
|
|
57625
58213
|
}
|
|
57626
58214
|
) });
|
|
57627
58215
|
};
|
|
58216
|
+
var TableEvidence = ({ columns, rows }) => {
|
|
58217
|
+
const formatValue = (value) => {
|
|
58218
|
+
if (typeof value === "number") {
|
|
58219
|
+
if (Number.isInteger(value)) return value.toString();
|
|
58220
|
+
return value.toFixed(3);
|
|
58221
|
+
}
|
|
58222
|
+
if (value === null || value === void 0) return "-";
|
|
58223
|
+
return String(value);
|
|
58224
|
+
};
|
|
58225
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-white border border-gray-100 rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "min-w-full text-xs", children: [
|
|
58226
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-gray-50 border-b border-gray-100", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
58227
|
+
"th",
|
|
58228
|
+
{
|
|
58229
|
+
className: "px-3 py-2 text-left font-semibold text-gray-600 uppercase tracking-wide",
|
|
58230
|
+
children: col.replace(/_/g, " ")
|
|
58231
|
+
},
|
|
58232
|
+
col
|
|
58233
|
+
)) }) }),
|
|
58234
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((row, idx) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-gray-100 last:border-b-0", children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-3 py-2 text-gray-700 whitespace-nowrap", children: formatValue(row[col]) }, col)) }, idx)) })
|
|
58235
|
+
] }) }) });
|
|
58236
|
+
};
|
|
57628
58237
|
var ImprovementCenterView = () => {
|
|
57629
58238
|
const { navigate } = useNavigation();
|
|
57630
|
-
const
|
|
57631
|
-
const
|
|
57632
|
-
const
|
|
57633
|
-
const
|
|
57634
|
-
|
|
57635
|
-
|
|
57636
|
-
|
|
57637
|
-
const
|
|
57638
|
-
|
|
57639
|
-
|
|
57640
|
-
|
|
57641
|
-
|
|
57642
|
-
|
|
57643
|
-
|
|
57644
|
-
|
|
58239
|
+
const supabase = useSupabase();
|
|
58240
|
+
const { user } = useAuth();
|
|
58241
|
+
const dashboardConfig = useDashboardConfig();
|
|
58242
|
+
const entityConfig = useEntityConfig();
|
|
58243
|
+
const [currentDate, setCurrentDate] = React24.useState(/* @__PURE__ */ new Date());
|
|
58244
|
+
const [selectedCategory, setSelectedCategory] = React24.useState("all");
|
|
58245
|
+
const [selectedLineId, setSelectedLineId] = React24.useState("all");
|
|
58246
|
+
const [selectedStatus, setSelectedStatus] = React24.useState("all");
|
|
58247
|
+
const [recommendations, setRecommendations] = React24.useState([]);
|
|
58248
|
+
const [isLoading, setIsLoading] = React24.useState(false);
|
|
58249
|
+
const [loadError, setLoadError] = React24.useState(null);
|
|
58250
|
+
const computeWeeksOpen = (firstSeenAt) => {
|
|
58251
|
+
if (!firstSeenAt) return void 0;
|
|
58252
|
+
const firstSeen = new Date(firstSeenAt);
|
|
58253
|
+
if (Number.isNaN(firstSeen.getTime())) return void 0;
|
|
58254
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
58255
|
+
const diffDays = Math.max(0, Math.floor((now2.getTime() - firstSeen.getTime()) / (1e3 * 60 * 60 * 24)));
|
|
58256
|
+
return Math.max(1, Math.ceil(diffDays / 7));
|
|
58257
|
+
};
|
|
58258
|
+
const configuredLines = React24.useMemo(() => {
|
|
58259
|
+
return entityConfig.lines || entityConfig.lineNames || {};
|
|
58260
|
+
}, [entityConfig.lines, entityConfig.lineNames]);
|
|
58261
|
+
const configuredLineIds = React24.useMemo(() => Object.keys(configuredLines), [configuredLines]);
|
|
58262
|
+
const { accessibleLineIds } = useUserLineAccess(configuredLineIds);
|
|
58263
|
+
const scopeLineIds = React24.useMemo(() => {
|
|
58264
|
+
return accessibleLineIds.length > 0 ? accessibleLineIds : configuredLineIds;
|
|
58265
|
+
}, [accessibleLineIds, configuredLineIds]);
|
|
58266
|
+
const scopeLineIdsKey = React24.useMemo(() => scopeLineIds.join(","), [scopeLineIds]);
|
|
58267
|
+
const companyId = user?.company_id || entityConfig.companyId;
|
|
58268
|
+
const clipsService = React24.useMemo(() => {
|
|
58269
|
+
try {
|
|
58270
|
+
return new S3ClipsSupabaseService(dashboardConfig);
|
|
58271
|
+
} catch (err) {
|
|
58272
|
+
return null;
|
|
58273
|
+
}
|
|
58274
|
+
}, [dashboardConfig]);
|
|
58275
|
+
const nextMonth = () => {
|
|
58276
|
+
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1));
|
|
58277
|
+
};
|
|
58278
|
+
const prevMonth = () => {
|
|
58279
|
+
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1));
|
|
58280
|
+
};
|
|
58281
|
+
const formatMonth = (date) => {
|
|
58282
|
+
return date.toLocaleDateString("en-US", { month: "long", year: "numeric" });
|
|
58283
|
+
};
|
|
58284
|
+
React24.useEffect(() => {
|
|
58285
|
+
let cancelled = false;
|
|
58286
|
+
const fetchRecommendations = async () => {
|
|
58287
|
+
if (!supabase || !companyId) return;
|
|
58288
|
+
setIsLoading(true);
|
|
58289
|
+
setLoadError(null);
|
|
58290
|
+
try {
|
|
58291
|
+
if (scopeLineIds.length === 0) {
|
|
58292
|
+
setRecommendations([]);
|
|
58293
|
+
return;
|
|
58294
|
+
}
|
|
58295
|
+
const params = new URLSearchParams({
|
|
58296
|
+
company_id: companyId,
|
|
58297
|
+
status: "all",
|
|
58298
|
+
limit: "500"
|
|
58299
|
+
});
|
|
58300
|
+
params.set("month", `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, "0")}`);
|
|
58301
|
+
if (scopeLineIds.length > 0) {
|
|
58302
|
+
params.set("line_ids", scopeLineIds.join(","));
|
|
58303
|
+
}
|
|
58304
|
+
const res = await fetchBackendJson(supabase, `/api/improvement-center/recommendations?${params.toString()}`);
|
|
58305
|
+
if (cancelled) return;
|
|
58306
|
+
let recs = res.recommendations || [];
|
|
58307
|
+
recs = recs.map((r2) => ({
|
|
58308
|
+
...r2,
|
|
58309
|
+
ticket_status: r2.issue_status === "resolved" ? "solved" : "active",
|
|
58310
|
+
weeks_open: typeof r2.weeks_open === "number" ? r2.weeks_open : computeWeeksOpen(r2.first_seen_at) ?? 1
|
|
58311
|
+
}));
|
|
58312
|
+
const allowed = new Set(scopeLineIds);
|
|
58313
|
+
setRecommendations(recs.filter((r2) => !r2.line_id || allowed.has(r2.line_id)));
|
|
58314
|
+
} catch (err) {
|
|
58315
|
+
if (cancelled) return;
|
|
58316
|
+
setLoadError(err?.message || "Failed to load recommendations");
|
|
58317
|
+
setRecommendations([]);
|
|
58318
|
+
} finally {
|
|
58319
|
+
if (!cancelled) setIsLoading(false);
|
|
57645
58320
|
}
|
|
58321
|
+
};
|
|
58322
|
+
fetchRecommendations();
|
|
58323
|
+
return () => {
|
|
58324
|
+
cancelled = true;
|
|
58325
|
+
};
|
|
58326
|
+
}, [supabase, companyId, scopeLineIdsKey, currentDate]);
|
|
58327
|
+
const filteredRecommendations = React24.useMemo(() => {
|
|
58328
|
+
return recommendations.filter((rec) => {
|
|
58329
|
+
if (selectedCategory !== "all" && rec.type !== selectedCategory) return false;
|
|
58330
|
+
if (selectedLineId !== "all" && rec.line_id !== selectedLineId) return false;
|
|
58331
|
+
if (selectedStatus === "resolved" && rec.ticket_status !== "solved") return false;
|
|
58332
|
+
if (selectedStatus === "unresolved" && rec.ticket_status === "solved") return false;
|
|
57646
58333
|
return true;
|
|
57647
|
-
});
|
|
57648
|
-
}, [
|
|
57649
|
-
const
|
|
57650
|
-
|
|
57651
|
-
|
|
58334
|
+
}).sort((a, b) => (b.weeks_open || 0) - (a.weeks_open || 0));
|
|
58335
|
+
}, [recommendations, selectedCategory, selectedLineId, selectedStatus]);
|
|
58336
|
+
const stats = React24.useMemo(() => {
|
|
58337
|
+
const total = recommendations.length;
|
|
58338
|
+
const resolved = recommendations.filter((r2) => r2.ticket_status === "solved").length;
|
|
58339
|
+
return {
|
|
58340
|
+
all: total,
|
|
58341
|
+
resolved,
|
|
58342
|
+
unresolved: total - resolved
|
|
58343
|
+
};
|
|
58344
|
+
}, [recommendations]);
|
|
57652
58345
|
const clearFilters = () => {
|
|
57653
|
-
|
|
57654
|
-
|
|
57655
|
-
|
|
57656
|
-
|
|
58346
|
+
setSelectedCategory("all");
|
|
58347
|
+
setSelectedLineId("all");
|
|
58348
|
+
setSelectedStatus("all");
|
|
58349
|
+
};
|
|
58350
|
+
const lineOptions = React24.useMemo(() => {
|
|
58351
|
+
const options = [{ id: "all", label: "All Lines" }];
|
|
58352
|
+
scopeLineIds.forEach((lineId) => {
|
|
58353
|
+
const lineName = configuredLines[lineId] || lineId;
|
|
58354
|
+
options.push({ id: lineId, label: lineName });
|
|
58355
|
+
});
|
|
58356
|
+
return options;
|
|
58357
|
+
}, [scopeLineIds, configuredLines]);
|
|
57657
58358
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
|
|
57658
58359
|
/* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
57659
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
58360
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4 min-w-[120px]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
57660
58361
|
BackButtonMinimal,
|
|
57661
58362
|
{
|
|
57662
58363
|
onClick: () => navigate("/"),
|
|
@@ -57664,121 +58365,190 @@ var ImprovementCenterView = () => {
|
|
|
57664
58365
|
}
|
|
57665
58366
|
) }),
|
|
57666
58367
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
|
|
57667
|
-
/* @__PURE__ */ jsxRuntime.
|
|
57668
|
-
|
|
57669
|
-
"Improvement Center"
|
|
57670
|
-
] }),
|
|
57671
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4", children: "Daily AI-driven recommendations to optimize your line performance" })
|
|
58368
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "Improvement Center" }),
|
|
58369
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center px-4 hidden sm:block", children: "Track and resolve persistent issues across your production lines" })
|
|
57672
58370
|
] }),
|
|
57673
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-
|
|
58371
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-[120px]" })
|
|
57674
58372
|
] }) }) }),
|
|
57675
|
-
/* @__PURE__ */ jsxRuntime.
|
|
57676
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
58373
|
+
/* @__PURE__ */ jsxRuntime.jsxs("main", { className: "flex-1 p-4 sm:p-6 max-w-7xl mx-auto w-full", children: [
|
|
58374
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 bg-white rounded-full px-4 py-2 border border-gray-200 shadow-sm", children: [
|
|
57677
58375
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
57678
|
-
"input",
|
|
57679
|
-
{
|
|
57680
|
-
ref: dateInputRef,
|
|
57681
|
-
type: "date",
|
|
57682
|
-
value: selectedDate,
|
|
57683
|
-
onChange: (e) => setSelectedDate(e.target.value),
|
|
57684
|
-
className: "sr-only"
|
|
57685
|
-
}
|
|
57686
|
-
),
|
|
57687
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
57688
58376
|
"button",
|
|
57689
58377
|
{
|
|
57690
|
-
onClick:
|
|
57691
|
-
className:
|
|
57692
|
-
children:
|
|
57693
|
-
/* @__PURE__ */ jsxRuntime.jsx(outline.CalendarIcon, { className: "w-4 h-4" }),
|
|
57694
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: selectedDate || "All Dates" })
|
|
57695
|
-
]
|
|
58378
|
+
onClick: prevMonth,
|
|
58379
|
+
className: "p-1 rounded-full hover:bg-gray-100 text-gray-500 transition-colors",
|
|
58380
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(outline.ChevronLeftIcon, { className: "w-5 h-5" })
|
|
57696
58381
|
}
|
|
57697
|
-
)
|
|
57698
|
-
|
|
57699
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
57700
|
-
"select",
|
|
57701
|
-
{
|
|
57702
|
-
value: selectedLine,
|
|
57703
|
-
onChange: (e) => setSelectedLine(e.target.value),
|
|
57704
|
-
className: `px-3 py-1.5 text-sm border rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer ${selectedLine ? "bg-blue-50 border-blue-200 text-blue-700" : "border-gray-300 text-gray-700"}`,
|
|
57705
|
-
children: [
|
|
57706
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All Lines" }),
|
|
57707
|
-
uniqueLines.map((line) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: line, children: line }, line))
|
|
57708
|
-
]
|
|
57709
|
-
}
|
|
57710
|
-
),
|
|
57711
|
-
hasActiveFilters && /* @__PURE__ */ jsxRuntime.jsx(
|
|
57712
|
-
"button",
|
|
57713
|
-
{
|
|
57714
|
-
onClick: clearFilters,
|
|
57715
|
-
className: "p-1.5 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100 transition-colors",
|
|
57716
|
-
title: "Clear filters",
|
|
57717
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(outline.XMarkIcon, { className: "w-5 h-5" })
|
|
57718
|
-
}
|
|
57719
|
-
)
|
|
57720
|
-
] }) }) }),
|
|
57721
|
-
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 p-4 sm:p-6 max-w-7xl mx-auto w-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6", children: [
|
|
57722
|
-
filteredRecommendations.length > 0 ? filteredRecommendations.map((rec, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
57723
|
-
motion.div,
|
|
57724
|
-
{
|
|
57725
|
-
initial: { opacity: 0, y: 20 },
|
|
57726
|
-
animate: { opacity: 1, y: 0 },
|
|
57727
|
-
transition: { delay: index * 0.1 },
|
|
57728
|
-
className: "bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden",
|
|
57729
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [
|
|
57730
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-4", children: [
|
|
57731
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
57732
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${rec.type === "cycle_time" ? "bg-blue-100 text-blue-800" : rec.type === "efficiency" ? "bg-orange-100 text-orange-800" : "bg-gray-100 text-gray-800"}`, children: rec.type.replace("_", " ").toUpperCase() }),
|
|
57733
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900 mt-2", children: rec.title }),
|
|
57734
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium text-gray-500", children: [
|
|
57735
|
-
rec.location,
|
|
57736
|
-
" \u2022 ",
|
|
57737
|
-
rec.line,
|
|
57738
|
-
" \u2022 ",
|
|
57739
|
-
rec.date
|
|
57740
|
-
] })
|
|
57741
|
-
] }) }),
|
|
57742
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "prose prose-sm text-gray-600", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: rec.description }) }),
|
|
57743
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-amber-50 border border-amber-100 rounded-lg p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-amber-800 font-medium flex items-start gap-2", children: [
|
|
57744
|
-
/* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-5 h-5 flex-shrink-0" }),
|
|
57745
|
-
"Impact: ",
|
|
57746
|
-
rec.impact
|
|
57747
|
-
] }) })
|
|
57748
|
-
] }),
|
|
57749
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full md:w-1/2 lg:w-5/12 bg-gray-50 rounded-lg p-4 border border-gray-100", children: [
|
|
57750
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
|
|
57751
|
-
/* @__PURE__ */ jsxRuntime.jsxs("h4", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider flex items-center gap-2", children: [
|
|
57752
|
-
rec.evidence.type === "video" ? /* @__PURE__ */ jsxRuntime.jsx(outline.PlayCircleIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-4 h-4" }),
|
|
57753
|
-
"Evidence: ",
|
|
57754
|
-
rec.evidence.label
|
|
57755
|
-
] }),
|
|
57756
|
-
rec.evidence.type === "video" && rec.evidence.videoUrls && rec.evidence.videoUrls.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] font-medium text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded", children: [
|
|
57757
|
-
rec.evidence.videoUrls.length,
|
|
57758
|
-
" clips"
|
|
57759
|
-
] })
|
|
57760
|
-
] }),
|
|
57761
|
-
rec.evidence.type === "video" && rec.evidence.videoUrls && /* @__PURE__ */ jsxRuntime.jsx(VideoCarousel, { urls: rec.evidence.videoUrls }),
|
|
57762
|
-
rec.evidence.type === "chart" && rec.evidence.chartData && /* @__PURE__ */ jsxRuntime.jsx(EvidenceChart, { data: rec.evidence.chartData })
|
|
57763
|
-
] })
|
|
57764
|
-
] }) })
|
|
57765
|
-
},
|
|
57766
|
-
rec.id
|
|
57767
|
-
)) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-16 bg-white rounded-xl border border-dashed border-gray-300", children: [
|
|
57768
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto flex items-center justify-center w-12 h-12 rounded-full bg-gray-100 mb-3", children: /* @__PURE__ */ jsxRuntime.jsx(outline.FunnelIcon, { className: "w-6 h-6 text-gray-400" }) }),
|
|
57769
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-gray-900", children: "No recommendations found" }),
|
|
57770
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Try adjusting your filters to see more results." }),
|
|
58382
|
+
),
|
|
58383
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-semibold text-gray-900 min-w-[160px] text-center", children: formatMonth(currentDate) }),
|
|
57771
58384
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
57772
58385
|
"button",
|
|
57773
58386
|
{
|
|
57774
|
-
onClick:
|
|
57775
|
-
className: "
|
|
57776
|
-
children: "
|
|
58387
|
+
onClick: nextMonth,
|
|
58388
|
+
className: "p-1 rounded-full hover:bg-gray-100 text-gray-500 transition-colors",
|
|
58389
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(outline.ChevronRightIcon, { className: "w-5 h-5" })
|
|
57777
58390
|
}
|
|
57778
58391
|
)
|
|
58392
|
+
] }) }),
|
|
58393
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row items-center justify-between gap-4 mb-6", children: [
|
|
58394
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
58395
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
58396
|
+
"button",
|
|
58397
|
+
{
|
|
58398
|
+
onClick: () => setSelectedStatus("all"),
|
|
58399
|
+
className: `px-4 py-2 rounded-lg text-sm font-medium transition-all cursor-pointer ${selectedStatus === "all" ? "bg-white text-gray-900 shadow-sm ring-1 ring-black/5" : "text-gray-500 hover:text-gray-700 hover:bg-gray-100"}`,
|
|
58400
|
+
children: [
|
|
58401
|
+
"All ",
|
|
58402
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1.5 bg-gray-100 px-2 py-0.5 rounded-full text-xs text-gray-600", children: stats.all })
|
|
58403
|
+
]
|
|
58404
|
+
}
|
|
58405
|
+
),
|
|
58406
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-px bg-gray-300 mx-1" }),
|
|
58407
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
58408
|
+
"button",
|
|
58409
|
+
{
|
|
58410
|
+
onClick: () => setSelectedStatus("resolved"),
|
|
58411
|
+
className: `flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm transition-all cursor-pointer ${selectedStatus === "resolved" ? "bg-green-50 text-green-700 ring-1 ring-green-200" : "text-gray-600 hover:bg-gray-100"}`,
|
|
58412
|
+
children: [
|
|
58413
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.CheckCircleIcon, { className: "w-4 h-4 text-green-500" }),
|
|
58414
|
+
"Resolved ",
|
|
58415
|
+
stats.resolved
|
|
58416
|
+
]
|
|
58417
|
+
}
|
|
58418
|
+
),
|
|
58419
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
58420
|
+
"button",
|
|
58421
|
+
{
|
|
58422
|
+
onClick: () => setSelectedStatus("unresolved"),
|
|
58423
|
+
className: `flex items-center gap-1.5 px-3 py-2 rounded-lg text-sm transition-all cursor-pointer ${selectedStatus === "unresolved" ? "bg-red-50 text-red-700 ring-1 ring-red-200" : "text-gray-600 hover:bg-gray-100"}`,
|
|
58424
|
+
children: [
|
|
58425
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.XCircleIcon, { className: "w-4 h-4 text-red-500" }),
|
|
58426
|
+
"Unresolved ",
|
|
58427
|
+
stats.unresolved
|
|
58428
|
+
]
|
|
58429
|
+
}
|
|
58430
|
+
)
|
|
58431
|
+
] }),
|
|
58432
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 w-full md:w-auto", children: [
|
|
58433
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
58434
|
+
"select",
|
|
58435
|
+
{
|
|
58436
|
+
value: selectedCategory,
|
|
58437
|
+
onChange: (e) => setSelectedCategory(e.target.value),
|
|
58438
|
+
className: "w-full md:w-auto appearance-none pl-3 pr-8 py-1.5 text-sm border border-gray-300 rounded-lg bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer text-gray-700 shadow-sm",
|
|
58439
|
+
style: { backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`, backgroundPosition: `right 0.5rem center`, backgroundRepeat: `no-repeat`, backgroundSize: `1.5em 1.5em` },
|
|
58440
|
+
children: CATEGORY_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.id, children: opt.label }, opt.id))
|
|
58441
|
+
}
|
|
58442
|
+
),
|
|
58443
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
58444
|
+
"select",
|
|
58445
|
+
{
|
|
58446
|
+
value: selectedLineId,
|
|
58447
|
+
onChange: (e) => setSelectedLineId(e.target.value),
|
|
58448
|
+
className: "w-full md:w-auto appearance-none pl-3 pr-8 py-1.5 text-sm border border-gray-300 rounded-lg bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer text-gray-700 shadow-sm",
|
|
58449
|
+
style: { backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`, backgroundPosition: `right 0.5rem center`, backgroundRepeat: `no-repeat`, backgroundSize: `1.5em 1.5em` },
|
|
58450
|
+
children: lineOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.id, children: opt.label }, opt.id))
|
|
58451
|
+
}
|
|
58452
|
+
)
|
|
58453
|
+
] })
|
|
57779
58454
|
] }),
|
|
57780
|
-
|
|
57781
|
-
|
|
58455
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6", children: [
|
|
58456
|
+
filteredRecommendations.length > 0 ? filteredRecommendations.map((rec, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
58457
|
+
motion.div,
|
|
58458
|
+
{
|
|
58459
|
+
initial: { opacity: 0, y: 20 },
|
|
58460
|
+
animate: { opacity: 1, y: 0 },
|
|
58461
|
+
transition: { delay: index * 0.1 },
|
|
58462
|
+
className: `bg-white rounded-xl shadow-sm border overflow-hidden ${rec.ticket_status === "solved" ? "border-green-200" : "border-gray-200"}`,
|
|
58463
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [
|
|
58464
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-4", children: [
|
|
58465
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start justify-between", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 w-full", children: [
|
|
58466
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
58467
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${rec.type === "cycle_time" ? "bg-blue-100 text-blue-800" : rec.type === "efficiency" ? "bg-orange-100 text-orange-800" : "bg-gray-100 text-gray-800"}`, children: rec.type.replace("_", " ").toUpperCase() }),
|
|
58468
|
+
(rec.shift_label || rec.shift_id !== void 0) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-700", children: rec.shift_label || `Shift ${rec.shift_id}` }),
|
|
58469
|
+
rec.ticket_status === "solved" ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800", children: [
|
|
58470
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.CheckCircleIcon, { className: "w-3.5 h-3.5" }),
|
|
58471
|
+
"Resolved"
|
|
58472
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium border ${(rec.weeks_open || 0) > 2 ? "bg-red-50 text-red-700 border-red-200" : "bg-amber-50 text-amber-700 border-amber-200"}`, children: [
|
|
58473
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.ClockIcon, { className: "w-3.5 h-3.5" }),
|
|
58474
|
+
"Open for ",
|
|
58475
|
+
rec.weeks_open || 1,
|
|
58476
|
+
" week",
|
|
58477
|
+
(rec.weeks_open || 1) !== 1 ? "s" : ""
|
|
58478
|
+
] })
|
|
58479
|
+
] }),
|
|
58480
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
58481
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: rec.title }),
|
|
58482
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium text-gray-500 mt-1", children: [
|
|
58483
|
+
rec.location,
|
|
58484
|
+
" \u2022 ",
|
|
58485
|
+
rec.line
|
|
58486
|
+
] })
|
|
58487
|
+
] })
|
|
58488
|
+
] }) }),
|
|
58489
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "prose prose-sm text-gray-600", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: rec.description }) }),
|
|
58490
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-amber-50 border border-amber-100 rounded-lg p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-amber-800 font-medium flex items-start gap-2", children: [
|
|
58491
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-5 h-5 flex-shrink-0" }),
|
|
58492
|
+
"Impact: ",
|
|
58493
|
+
rec.impact
|
|
58494
|
+
] }) })
|
|
58495
|
+
] }),
|
|
58496
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full md:w-1/2 lg:w-5/12 bg-gray-50 rounded-lg p-4 border border-gray-100", children: Array.isArray(rec.evidence) && rec.evidence.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: rec.evidence.map((ev, idx) => {
|
|
58497
|
+
const isVideo = ev.type === "video_gallery";
|
|
58498
|
+
const isChart = ev.type === "bar_chart" || ev.type === "timeseries" || ev.type === "pie_chart";
|
|
58499
|
+
const title = ev.title || "Evidence";
|
|
58500
|
+
const clips = Array.isArray(ev.clips) ? ev.clips : [];
|
|
58501
|
+
const clipCount = clips.length;
|
|
58502
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
58503
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
58504
|
+
/* @__PURE__ */ jsxRuntime.jsxs("h4", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider flex items-center gap-2", children: [
|
|
58505
|
+
isVideo ? /* @__PURE__ */ jsxRuntime.jsx(outline.PlayCircleIcon, { className: "w-4 h-4" }) : isChart ? /* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-4 h-4" }),
|
|
58506
|
+
title
|
|
58507
|
+
] }),
|
|
58508
|
+
isVideo && clipCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] font-medium text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded", children: [
|
|
58509
|
+
clipCount,
|
|
58510
|
+
" clips"
|
|
58511
|
+
] })
|
|
58512
|
+
] }),
|
|
58513
|
+
ev.type === "video_gallery" && /* @__PURE__ */ jsxRuntime.jsx(ClipVideoCarousel, { clips, clipsService }),
|
|
58514
|
+
ev.type === "bar_chart" && (Array.isArray(ev.x) && Array.isArray(ev.y) || Array.isArray(ev.data) && Array.isArray(ev.series)) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
58515
|
+
BarChartEvidence,
|
|
58516
|
+
{
|
|
58517
|
+
x: ev.x,
|
|
58518
|
+
y: ev.y,
|
|
58519
|
+
data: ev.data,
|
|
58520
|
+
series: ev.series,
|
|
58521
|
+
unit: ev.unit,
|
|
58522
|
+
referenceLines: Array.isArray(ev.reference_lines) ? ev.reference_lines : void 0
|
|
58523
|
+
}
|
|
58524
|
+
),
|
|
58525
|
+
ev.type === "pie_chart" && Array.isArray(ev.data) && /* @__PURE__ */ jsxRuntime.jsx(PieChartEvidence, { data: ev.data }),
|
|
58526
|
+
ev.type === "timeseries" && Array.isArray(ev.x) && Array.isArray(ev.y) && /* @__PURE__ */ jsxRuntime.jsx(LineChartEvidence, { x: ev.x, y: ev.y, unit: ev.unit }),
|
|
58527
|
+
ev.type === "table" && Array.isArray(ev.columns) && Array.isArray(ev.rows) && /* @__PURE__ */ jsxRuntime.jsx(TableEvidence, { columns: ev.columns, rows: ev.rows })
|
|
58528
|
+
] }, `${rec.id}-ev-${idx}`);
|
|
58529
|
+
}) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: isLoading ? "Loading evidence\u2026" : loadError ? loadError : "No evidence available" }) })
|
|
58530
|
+
] }) })
|
|
58531
|
+
},
|
|
58532
|
+
rec.id
|
|
58533
|
+
)) : (
|
|
58534
|
+
// Success State (Zero Tickets)
|
|
58535
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-16 bg-white rounded-xl border border-gray-200 shadow-sm", children: [
|
|
58536
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto flex items-center justify-center w-16 h-16 rounded-full bg-green-50 mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(outline.CheckCircleIcon, { className: "w-8 h-8 text-green-500" }) }),
|
|
58537
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: "No tickets found" }),
|
|
58538
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-gray-500 max-w-md mx-auto", children: selectedCategory !== "all" || selectedLineId !== "all" || selectedStatus !== "all" ? "Try adjusting your filters to see more results." : "All monitored long-term patterns were evaluated and remained within limits. This is a success state!" }),
|
|
58539
|
+
(selectedCategory !== "all" || selectedLineId !== "all" || selectedStatus !== "all") && /* @__PURE__ */ jsxRuntime.jsx(
|
|
58540
|
+
"button",
|
|
58541
|
+
{
|
|
58542
|
+
onClick: clearFilters,
|
|
58543
|
+
className: "mt-6 text-sm text-blue-600 hover:text-blue-800 font-medium bg-blue-50 px-4 py-2 rounded-lg hover:bg-blue-100 transition-colors",
|
|
58544
|
+
children: "Clear filters"
|
|
58545
|
+
}
|
|
58546
|
+
)
|
|
58547
|
+
] })
|
|
58548
|
+
),
|
|
58549
|
+
filteredRecommendations.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-400 text-sm", children: "More recommendations will appear here as we analyze more data." }) })
|
|
58550
|
+
] })
|
|
58551
|
+
] })
|
|
57782
58552
|
] });
|
|
57783
58553
|
};
|
|
57784
58554
|
var ImprovementCenterView_default = ImprovementCenterView;
|
|
@@ -58237,177 +59007,6 @@ var streamProxyConfig = {
|
|
|
58237
59007
|
}
|
|
58238
59008
|
};
|
|
58239
59009
|
|
|
58240
|
-
// src/lib/api/s3-clips-parser.ts
|
|
58241
|
-
function parseS3Uri(s3Uri, sopCategories) {
|
|
58242
|
-
const path = new URL(s3Uri).pathname;
|
|
58243
|
-
const parts = path.split("/").filter((p) => p);
|
|
58244
|
-
if (s3Uri.includes("missed_qchecks")) {
|
|
58245
|
-
console.warn(`Skipping missed_qchecks URI in parseS3Uri: ${s3Uri}`);
|
|
58246
|
-
return null;
|
|
58247
|
-
}
|
|
58248
|
-
if (parts.length < 8) {
|
|
58249
|
-
console.warn(`Invalid S3 path structure: ${s3Uri} - Too few parts: ${parts.length}, expected at least 8`);
|
|
58250
|
-
return null;
|
|
58251
|
-
}
|
|
58252
|
-
try {
|
|
58253
|
-
const datePart = parts[2];
|
|
58254
|
-
const shiftPart = parts[3];
|
|
58255
|
-
const violationType = parts[4];
|
|
58256
|
-
let folderName = "";
|
|
58257
|
-
let timestamp = "";
|
|
58258
|
-
for (let i = 5; i < parts.length; i++) {
|
|
58259
|
-
const part = parts[i];
|
|
58260
|
-
if (part && part.includes("_") && /\d{8}_\d{6}/.test(part)) {
|
|
58261
|
-
folderName = part;
|
|
58262
|
-
const timeMatch = folderName.match(/_(\d{8})_(\d{6})_/);
|
|
58263
|
-
if (timeMatch) {
|
|
58264
|
-
timestamp = `${timeMatch[2].substring(0, 2)}:${timeMatch[2].substring(2, 4)}:${timeMatch[2].substring(4, 6)}`;
|
|
58265
|
-
break;
|
|
58266
|
-
}
|
|
58267
|
-
}
|
|
58268
|
-
}
|
|
58269
|
-
if (!timestamp) {
|
|
58270
|
-
console.warn(`Couldn't extract timestamp from any part: ${parts.join("/")}`);
|
|
58271
|
-
timestamp = "00:00:00";
|
|
58272
|
-
}
|
|
58273
|
-
let severity = "low";
|
|
58274
|
-
let type = "bottleneck";
|
|
58275
|
-
let description = "Analysis Clip";
|
|
58276
|
-
const normalizedViolationType = violationType.toLowerCase().trim();
|
|
58277
|
-
if (sopCategories && sopCategories.length > 0) {
|
|
58278
|
-
const matchedCategory = sopCategories.find((category) => {
|
|
58279
|
-
const categoryId = category.id.toLowerCase();
|
|
58280
|
-
const s3FolderName = (category.s3FolderName || category.id).toLowerCase();
|
|
58281
|
-
return categoryId === normalizedViolationType || s3FolderName === normalizedViolationType || // Also check for partial matches for flexibility
|
|
58282
|
-
normalizedViolationType.includes(categoryId) || normalizedViolationType.includes(s3FolderName);
|
|
58283
|
-
});
|
|
58284
|
-
if (matchedCategory) {
|
|
58285
|
-
type = matchedCategory.id;
|
|
58286
|
-
description = matchedCategory.description || matchedCategory.label;
|
|
58287
|
-
if (matchedCategory.color.includes("red")) {
|
|
58288
|
-
severity = "high";
|
|
58289
|
-
} else if (matchedCategory.color.includes("yellow") || matchedCategory.color.includes("orange")) {
|
|
58290
|
-
severity = "medium";
|
|
58291
|
-
} else {
|
|
58292
|
-
severity = "low";
|
|
58293
|
-
}
|
|
58294
|
-
console.log(`Matched SOP category: ${matchedCategory.id} for violation type: ${violationType}`);
|
|
58295
|
-
return { timestamp, severity, description, type, originalUri: s3Uri };
|
|
58296
|
-
}
|
|
58297
|
-
}
|
|
58298
|
-
switch (normalizedViolationType) {
|
|
58299
|
-
case "idle_time":
|
|
58300
|
-
case "idle":
|
|
58301
|
-
case "low_value":
|
|
58302
|
-
case "low value":
|
|
58303
|
-
case "low_value_moment":
|
|
58304
|
-
case "low_value_moments":
|
|
58305
|
-
case "low value moment":
|
|
58306
|
-
case "low value moments":
|
|
58307
|
-
type = "low_value";
|
|
58308
|
-
severity = "low";
|
|
58309
|
-
description = "Idle Time Detected";
|
|
58310
|
-
break;
|
|
58311
|
-
case "sop_deviation":
|
|
58312
|
-
type = "missing_quality_check";
|
|
58313
|
-
severity = "high";
|
|
58314
|
-
description = "SOP Deviations";
|
|
58315
|
-
break;
|
|
58316
|
-
case "long_cycle_time":
|
|
58317
|
-
severity = "high";
|
|
58318
|
-
type = "long_cycle_time";
|
|
58319
|
-
description = "Long Cycle Time Detected";
|
|
58320
|
-
break;
|
|
58321
|
-
case "best_cycle_time":
|
|
58322
|
-
type = "best_cycle_time";
|
|
58323
|
-
severity = "low";
|
|
58324
|
-
description = "Best Cycle Time Performance";
|
|
58325
|
-
break;
|
|
58326
|
-
case "worst_cycle_time":
|
|
58327
|
-
type = "worst_cycle_time";
|
|
58328
|
-
severity = "high";
|
|
58329
|
-
description = "Worst Cycle Time Performance";
|
|
58330
|
-
break;
|
|
58331
|
-
case "cycle_completion":
|
|
58332
|
-
case "completed_cycles":
|
|
58333
|
-
case "completed_cycle":
|
|
58334
|
-
type = "cycle_completion";
|
|
58335
|
-
severity = "low";
|
|
58336
|
-
description = "Cycle Completion";
|
|
58337
|
-
break;
|
|
58338
|
-
case "running_cycle":
|
|
58339
|
-
case "active_cycle":
|
|
58340
|
-
case "production_cycle":
|
|
58341
|
-
type = "running_cycle";
|
|
58342
|
-
severity = "low";
|
|
58343
|
-
description = "Active Production Cycle";
|
|
58344
|
-
break;
|
|
58345
|
-
case "setup_state":
|
|
58346
|
-
case "machine_setup":
|
|
58347
|
-
case "line_setup":
|
|
58348
|
-
type = "setup_state";
|
|
58349
|
-
severity = "medium";
|
|
58350
|
-
description = "Machine Setup Activity";
|
|
58351
|
-
break;
|
|
58352
|
-
case "medium_bottleneck":
|
|
58353
|
-
severity = "medium";
|
|
58354
|
-
description = "Medium Bottleneck Identified";
|
|
58355
|
-
break;
|
|
58356
|
-
case "minor_bottleneck":
|
|
58357
|
-
case "mild_bottleneck":
|
|
58358
|
-
severity = "low";
|
|
58359
|
-
description = "Minor Bottleneck Identified";
|
|
58360
|
-
break;
|
|
58361
|
-
default:
|
|
58362
|
-
if (normalizedViolationType.includes("sop") && normalizedViolationType.includes("deviation")) {
|
|
58363
|
-
type = "missing_quality_check";
|
|
58364
|
-
severity = "high";
|
|
58365
|
-
description = "SOP Deviations";
|
|
58366
|
-
} else if (normalizedViolationType.includes("worst") && normalizedViolationType.includes("cycle")) {
|
|
58367
|
-
type = "worst_cycle_time";
|
|
58368
|
-
severity = "high";
|
|
58369
|
-
description = "Worst Cycle Time Performance";
|
|
58370
|
-
} else if (normalizedViolationType.includes("best") && normalizedViolationType.includes("cycle")) {
|
|
58371
|
-
type = "best_cycle_time";
|
|
58372
|
-
severity = "low";
|
|
58373
|
-
description = "Best Cycle Time Performance";
|
|
58374
|
-
} else if (normalizedViolationType.includes("long") && normalizedViolationType.includes("cycle")) {
|
|
58375
|
-
type = "long_cycle_time";
|
|
58376
|
-
severity = "high";
|
|
58377
|
-
description = "Long Cycle Time Detected";
|
|
58378
|
-
} else if (normalizedViolationType.includes("cycle") && (normalizedViolationType.includes("completion") || normalizedViolationType.includes("complete"))) {
|
|
58379
|
-
type = "cycle_completion";
|
|
58380
|
-
severity = "low";
|
|
58381
|
-
description = "Cycle Completion";
|
|
58382
|
-
} else if (normalizedViolationType.includes("running") && normalizedViolationType.includes("cycle")) {
|
|
58383
|
-
type = "running_cycle";
|
|
58384
|
-
severity = "low";
|
|
58385
|
-
description = "Active Production Cycle";
|
|
58386
|
-
} else if (normalizedViolationType.includes("setup") || normalizedViolationType.includes("machine") && normalizedViolationType.includes("setup")) {
|
|
58387
|
-
type = "setup_state";
|
|
58388
|
-
severity = "medium";
|
|
58389
|
-
description = "Machine Setup Activity";
|
|
58390
|
-
} else {
|
|
58391
|
-
description = `Clip type: ${violationType.replace(/_/g, " ")}`;
|
|
58392
|
-
console.log(`Detected unknown violation type: ${violationType} in URI: ${s3Uri}`);
|
|
58393
|
-
}
|
|
58394
|
-
break;
|
|
58395
|
-
}
|
|
58396
|
-
return { timestamp, severity, description, type, originalUri: s3Uri };
|
|
58397
|
-
} catch (error) {
|
|
58398
|
-
console.error(`Error parsing S3 URI: ${s3Uri}`, error);
|
|
58399
|
-
return null;
|
|
58400
|
-
}
|
|
58401
|
-
}
|
|
58402
|
-
function shuffleArray(array) {
|
|
58403
|
-
const shuffled = [...array];
|
|
58404
|
-
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
58405
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
58406
|
-
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
58407
|
-
}
|
|
58408
|
-
return shuffled;
|
|
58409
|
-
}
|
|
58410
|
-
|
|
58411
59010
|
exports.ACTION_NAMES = ACTION_NAMES;
|
|
58412
59011
|
exports.AIAgentView = AIAgentView_default;
|
|
58413
59012
|
exports.AcceptInvite = AcceptInvite;
|