@optifye/dashboard-core 4.1.1 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +31 -2
- package/dist/index.d.ts +31 -2
- package/dist/index.js +443 -78
- package/dist/index.mjs +443 -78
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25337,6 +25337,32 @@ var S3ClipsService = class {
|
|
|
25337
25337
|
return null;
|
|
25338
25338
|
}
|
|
25339
25339
|
}
|
|
25340
|
+
/**
|
|
25341
|
+
* Fetches full metadata including timestamps
|
|
25342
|
+
*/
|
|
25343
|
+
async getFullMetadata(playlistUri) {
|
|
25344
|
+
try {
|
|
25345
|
+
const metadataUri = playlistUri.replace(/playlist\.m3u8$/, "metadata.json");
|
|
25346
|
+
const url = new URL(metadataUri);
|
|
25347
|
+
const bucket = url.hostname;
|
|
25348
|
+
const key = url.pathname.substring(1);
|
|
25349
|
+
const command = new clientS3.GetObjectCommand({
|
|
25350
|
+
Bucket: bucket,
|
|
25351
|
+
Key: key
|
|
25352
|
+
});
|
|
25353
|
+
const response = await this.s3Client.send(command);
|
|
25354
|
+
if (!response.Body) {
|
|
25355
|
+
console.warn(`Empty response body for metadata file: ${key}`);
|
|
25356
|
+
return null;
|
|
25357
|
+
}
|
|
25358
|
+
const metadataContent = await response.Body.transformToString();
|
|
25359
|
+
const metadata = JSON.parse(metadataContent);
|
|
25360
|
+
return metadata;
|
|
25361
|
+
} catch (error) {
|
|
25362
|
+
console.error(`Error fetching or parsing metadata:`, error);
|
|
25363
|
+
return null;
|
|
25364
|
+
}
|
|
25365
|
+
}
|
|
25340
25366
|
/**
|
|
25341
25367
|
* Converts S3 URI to CloudFront URL
|
|
25342
25368
|
* Uses streaming proxy for localhost development to handle CORS
|
|
@@ -25349,15 +25375,22 @@ var S3ClipsService = class {
|
|
|
25349
25375
|
/**
|
|
25350
25376
|
* Processes a single video completely
|
|
25351
25377
|
*/
|
|
25352
|
-
async processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime) {
|
|
25378
|
+
async processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime, includeMetadata = false) {
|
|
25353
25379
|
const parsedInfo = parseS3Uri(uri);
|
|
25354
25380
|
if (!parsedInfo) {
|
|
25355
25381
|
console.warn(`Skipping URI due to parsing failure: ${uri}`);
|
|
25356
25382
|
return null;
|
|
25357
25383
|
}
|
|
25358
25384
|
let cycleTimeSeconds = null;
|
|
25359
|
-
|
|
25360
|
-
|
|
25385
|
+
let creationTimestamp = void 0;
|
|
25386
|
+
if (includeMetadata || includeCycleTime && (parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time") || parsedInfo.type === "best_cycle_time" || parsedInfo.type === "worst_cycle_time")) {
|
|
25387
|
+
const metadata = await this.getFullMetadata(uri);
|
|
25388
|
+
if (metadata) {
|
|
25389
|
+
if (metadata.original_task_metadata?.cycle_time) {
|
|
25390
|
+
cycleTimeSeconds = metadata.original_task_metadata.cycle_time;
|
|
25391
|
+
}
|
|
25392
|
+
creationTimestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp || metadata[""];
|
|
25393
|
+
}
|
|
25361
25394
|
}
|
|
25362
25395
|
const cloudfrontPlaylistUrl = this.s3UriToCloudfront(uri);
|
|
25363
25396
|
const { type: videoType, timestamp: videoTimestamp } = parsedInfo;
|
|
@@ -25366,7 +25399,8 @@ var S3ClipsService = class {
|
|
|
25366
25399
|
src: cloudfrontPlaylistUrl,
|
|
25367
25400
|
// Direct CloudFront playlist URL
|
|
25368
25401
|
...parsedInfo,
|
|
25369
|
-
cycle_time_seconds: cycleTimeSeconds || void 0
|
|
25402
|
+
cycle_time_seconds: cycleTimeSeconds || void 0,
|
|
25403
|
+
creation_timestamp: creationTimestamp
|
|
25370
25404
|
};
|
|
25371
25405
|
}
|
|
25372
25406
|
/**
|
|
@@ -25428,7 +25462,7 @@ var S3ClipsService = class {
|
|
|
25428
25462
|
* Main method to fetch clips based on parameters
|
|
25429
25463
|
*/
|
|
25430
25464
|
async fetchClips(params) {
|
|
25431
|
-
const { workspaceId, date: inputDate, shift, category, limit, mode, includeCycleTime } = params;
|
|
25465
|
+
const { workspaceId, date: inputDate, shift, category, limit, mode, includeCycleTime, includeMetadata, timestampStart, timestampEnd } = params;
|
|
25432
25466
|
if (!workspaceId) {
|
|
25433
25467
|
throw new Error("Valid Workspace ID is required");
|
|
25434
25468
|
}
|
|
@@ -25533,7 +25567,7 @@ var S3ClipsService = class {
|
|
|
25533
25567
|
const batch = filteredUris.slice(i, i + concurrencyLimit);
|
|
25534
25568
|
const batchPromises = batch.map(async (uri, batchIndex) => {
|
|
25535
25569
|
const index = i + batchIndex;
|
|
25536
|
-
const result = await this.processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime || false);
|
|
25570
|
+
const result = await this.processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime || false, includeMetadata || (!!timestampStart || !!timestampEnd));
|
|
25537
25571
|
processedCount++;
|
|
25538
25572
|
if (processedCount % 10 === 0) {
|
|
25539
25573
|
console.log(`S3ClipsService: Processed ${processedCount}/${filteredUris.length} videos for category '${category || "all"}'...`);
|
|
@@ -25543,7 +25577,26 @@ var S3ClipsService = class {
|
|
|
25543
25577
|
const batchResults = await Promise.all(batchPromises);
|
|
25544
25578
|
videoResults.push(...batchResults);
|
|
25545
25579
|
}
|
|
25546
|
-
|
|
25580
|
+
let videos = videoResults.filter((v) => v !== null);
|
|
25581
|
+
if (timestampStart || timestampEnd) {
|
|
25582
|
+
videos = videos.filter((video) => {
|
|
25583
|
+
if (!video.creation_timestamp) return false;
|
|
25584
|
+
const videoTimestamp = new Date(video.creation_timestamp).getTime();
|
|
25585
|
+
if (timestampStart && timestampEnd) {
|
|
25586
|
+
const start = new Date(timestampStart).getTime();
|
|
25587
|
+
const end = new Date(timestampEnd).getTime();
|
|
25588
|
+
return videoTimestamp >= start && videoTimestamp <= end;
|
|
25589
|
+
} else if (timestampStart) {
|
|
25590
|
+
const start = new Date(timestampStart).getTime();
|
|
25591
|
+
return videoTimestamp >= start;
|
|
25592
|
+
} else if (timestampEnd) {
|
|
25593
|
+
const end = new Date(timestampEnd).getTime();
|
|
25594
|
+
return videoTimestamp <= end;
|
|
25595
|
+
}
|
|
25596
|
+
return true;
|
|
25597
|
+
});
|
|
25598
|
+
console.log(`S3ClipsService: Filtered by timestamp - ${videos.length} videos remain after filtering`);
|
|
25599
|
+
}
|
|
25547
25600
|
console.log(`S3ClipsService: Successfully processed ${videos.length} out of ${filteredUris.length} video clips for category '${category || "all"}' (limit: ${limitPerCategory} per category).`);
|
|
25548
25601
|
const typeCounts = videos.reduce((acc, video) => {
|
|
25549
25602
|
acc[video.type] = (acc[video.type] || 0) + 1;
|
|
@@ -25588,6 +25641,7 @@ var BottlenecksContent = ({
|
|
|
25588
25641
|
const dashboardConfig = useDashboardConfig();
|
|
25589
25642
|
const videoRef = React46.useRef(null);
|
|
25590
25643
|
const fullscreenContainerRef = React46.useRef(null);
|
|
25644
|
+
const timestampFilterRef = React46.useRef(null);
|
|
25591
25645
|
const [isPlaying, setIsPlaying] = React46.useState(false);
|
|
25592
25646
|
const [currentTime, setCurrentTime] = React46.useState(0);
|
|
25593
25647
|
const [duration, setDuration] = React46.useState(0);
|
|
@@ -25597,6 +25651,22 @@ var BottlenecksContent = ({
|
|
|
25597
25651
|
const [allVideos, setAllVideos] = React46.useState([]);
|
|
25598
25652
|
const [isLoading, setIsLoading] = React46.useState(true);
|
|
25599
25653
|
const [error, setError] = React46.useState(null);
|
|
25654
|
+
const [showTimestampFilter, setShowTimestampFilter] = React46.useState(false);
|
|
25655
|
+
const [timestampStart, setTimestampStart] = React46.useState("");
|
|
25656
|
+
const [timestampEnd, setTimestampEnd] = React46.useState("");
|
|
25657
|
+
React46.useEffect(() => {
|
|
25658
|
+
const handleClickOutside = (event) => {
|
|
25659
|
+
if (timestampFilterRef.current && !timestampFilterRef.current.contains(event.target)) {
|
|
25660
|
+
setShowTimestampFilter(false);
|
|
25661
|
+
}
|
|
25662
|
+
};
|
|
25663
|
+
if (showTimestampFilter) {
|
|
25664
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
25665
|
+
}
|
|
25666
|
+
return () => {
|
|
25667
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
25668
|
+
};
|
|
25669
|
+
}, [showTimestampFilter]);
|
|
25600
25670
|
const s3ClipsService = React46.useMemo(() => {
|
|
25601
25671
|
if (!dashboardConfig?.s3Config) {
|
|
25602
25672
|
console.warn("S3 configuration not found in dashboard config");
|
|
@@ -25609,13 +25679,26 @@ var BottlenecksContent = ({
|
|
|
25609
25679
|
setIsLoading(true);
|
|
25610
25680
|
setError(null);
|
|
25611
25681
|
try {
|
|
25682
|
+
const operationalDate = date || getOperationalDate();
|
|
25683
|
+
let timestampStartFull;
|
|
25684
|
+
let timestampEndFull;
|
|
25685
|
+
if (timestampStart) {
|
|
25686
|
+
timestampStartFull = `${operationalDate}T${timestampStart}:00`;
|
|
25687
|
+
}
|
|
25688
|
+
if (timestampEnd) {
|
|
25689
|
+
timestampEndFull = `${operationalDate}T${timestampEnd}:00`;
|
|
25690
|
+
}
|
|
25612
25691
|
const videos = await s3ClipsService.fetchClips({
|
|
25613
25692
|
workspaceId,
|
|
25614
|
-
date:
|
|
25693
|
+
date: operationalDate,
|
|
25615
25694
|
mode: "full",
|
|
25616
25695
|
includeCycleTime: true,
|
|
25617
|
-
|
|
25696
|
+
includeMetadata: true,
|
|
25697
|
+
// Always include metadata for timestamp info
|
|
25698
|
+
limit: 50,
|
|
25618
25699
|
// Reasonable limit for UI performance
|
|
25700
|
+
timestampStart: timestampStartFull,
|
|
25701
|
+
timestampEnd: timestampEndFull
|
|
25619
25702
|
});
|
|
25620
25703
|
if (Array.isArray(videos) && videos.length > 0) {
|
|
25621
25704
|
preloadVideoUrl2(videos[0].src);
|
|
@@ -25643,7 +25726,7 @@ var BottlenecksContent = ({
|
|
|
25643
25726
|
} finally {
|
|
25644
25727
|
setIsLoading(false);
|
|
25645
25728
|
}
|
|
25646
|
-
}, [workspaceId, date, s3ClipsService]);
|
|
25729
|
+
}, [workspaceId, date, s3ClipsService, timestampStart, timestampEnd]);
|
|
25647
25730
|
React46.useEffect(() => {
|
|
25648
25731
|
if (s3ClipsService) {
|
|
25649
25732
|
fetchClips();
|
|
@@ -26012,6 +26095,43 @@ var BottlenecksContent = ({
|
|
|
26012
26095
|
return "Bottleneck";
|
|
26013
26096
|
}
|
|
26014
26097
|
};
|
|
26098
|
+
const formatTimestamp = (timestamp) => {
|
|
26099
|
+
if (!timestamp) return "";
|
|
26100
|
+
try {
|
|
26101
|
+
const date2 = new Date(timestamp);
|
|
26102
|
+
const today = /* @__PURE__ */ new Date();
|
|
26103
|
+
const isToday = date2.toDateString() === today.toDateString();
|
|
26104
|
+
if (isToday) {
|
|
26105
|
+
return date2.toLocaleString("en-US", {
|
|
26106
|
+
hour: "numeric",
|
|
26107
|
+
minute: "2-digit",
|
|
26108
|
+
hour12: true
|
|
26109
|
+
});
|
|
26110
|
+
} else {
|
|
26111
|
+
return date2.toLocaleString("en-US", {
|
|
26112
|
+
month: "short",
|
|
26113
|
+
day: "numeric",
|
|
26114
|
+
hour: "numeric",
|
|
26115
|
+
minute: "2-digit",
|
|
26116
|
+
hour12: true
|
|
26117
|
+
});
|
|
26118
|
+
}
|
|
26119
|
+
} catch {
|
|
26120
|
+
return "";
|
|
26121
|
+
}
|
|
26122
|
+
};
|
|
26123
|
+
const formatTimeOnly = (time2) => {
|
|
26124
|
+
if (!time2) return "";
|
|
26125
|
+
try {
|
|
26126
|
+
const [hours, minutes] = time2.split(":");
|
|
26127
|
+
const hour = parseInt(hours);
|
|
26128
|
+
const ampm = hour >= 12 ? "PM" : "AM";
|
|
26129
|
+
const displayHour = hour % 12 || 12;
|
|
26130
|
+
return `${displayHour}:${minutes} ${ampm}`;
|
|
26131
|
+
} catch {
|
|
26132
|
+
return time2;
|
|
26133
|
+
}
|
|
26134
|
+
};
|
|
26015
26135
|
if (!dashboardConfig?.s3Config) {
|
|
26016
26136
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-grow p-4 flex flex-col items-center justify-center h-[calc(100vh-12rem)] text-center", children: [
|
|
26017
26137
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
|
|
@@ -26120,6 +26240,74 @@ var BottlenecksContent = ({
|
|
|
26120
26240
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 border-b border-gray-100", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
26121
26241
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-800", children: activeFilter === "low_value" ? `Low Value Moments (${clipCounts.lowValue})` : activeFilter === "best_cycle_time" ? `Best Cycle Times (${clipCounts.bestCycleTimes})` : activeFilter === "worst_cycle_time" ? `Worst Cycle Times (${clipCounts.worstCycleTimes})` : activeFilter === "long_cycle_time" ? `Long Cycle Time (${clipCounts.longCycleTimes})` : `All Clips (${clipCounts.total})` }),
|
|
26122
26242
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
26243
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", ref: timestampFilterRef, children: [
|
|
26244
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26245
|
+
"button",
|
|
26246
|
+
{
|
|
26247
|
+
onClick: () => setShowTimestampFilter(!showTimestampFilter),
|
|
26248
|
+
className: `p-2 rounded-lg transition-all ${timestampStart || timestampEnd ? "bg-blue-50 text-blue-600 hover:bg-blue-100" : "text-gray-600 hover:bg-gray-100"}`,
|
|
26249
|
+
"aria-label": "Filter by time",
|
|
26250
|
+
title: "Filter by time",
|
|
26251
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-5 w-5" })
|
|
26252
|
+
}
|
|
26253
|
+
),
|
|
26254
|
+
showTimestampFilter && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 mt-2 w-80 bg-white rounded-lg shadow-lg border border-gray-200 p-4 z-20", children: [
|
|
26255
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold text-gray-700 mb-3", children: "Filter by Time" }),
|
|
26256
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
26257
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
26258
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "timestamp-start", className: "block text-xs font-medium text-gray-600 mb-1", children: "Start Time" }),
|
|
26259
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26260
|
+
"input",
|
|
26261
|
+
{
|
|
26262
|
+
id: "timestamp-start",
|
|
26263
|
+
type: "time",
|
|
26264
|
+
value: timestampStart,
|
|
26265
|
+
onChange: (e) => setTimestampStart(e.target.value),
|
|
26266
|
+
className: "w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
26267
|
+
}
|
|
26268
|
+
)
|
|
26269
|
+
] }),
|
|
26270
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
26271
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "timestamp-end", className: "block text-xs font-medium text-gray-600 mb-1", children: "End Time" }),
|
|
26272
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26273
|
+
"input",
|
|
26274
|
+
{
|
|
26275
|
+
id: "timestamp-end",
|
|
26276
|
+
type: "time",
|
|
26277
|
+
value: timestampEnd,
|
|
26278
|
+
onChange: (e) => setTimestampEnd(e.target.value),
|
|
26279
|
+
className: "w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
26280
|
+
}
|
|
26281
|
+
)
|
|
26282
|
+
] }),
|
|
26283
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between pt-2", children: [
|
|
26284
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26285
|
+
"button",
|
|
26286
|
+
{
|
|
26287
|
+
onClick: () => {
|
|
26288
|
+
setTimestampStart("");
|
|
26289
|
+
setTimestampEnd("");
|
|
26290
|
+
setShowTimestampFilter(false);
|
|
26291
|
+
},
|
|
26292
|
+
className: "px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 transition-colors",
|
|
26293
|
+
children: "Clear"
|
|
26294
|
+
}
|
|
26295
|
+
),
|
|
26296
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26297
|
+
"button",
|
|
26298
|
+
{
|
|
26299
|
+
onClick: () => {
|
|
26300
|
+
setShowTimestampFilter(false);
|
|
26301
|
+
fetchClips();
|
|
26302
|
+
},
|
|
26303
|
+
className: "px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors",
|
|
26304
|
+
children: "Apply Filter"
|
|
26305
|
+
}
|
|
26306
|
+
)
|
|
26307
|
+
] })
|
|
26308
|
+
] })
|
|
26309
|
+
] })
|
|
26310
|
+
] }),
|
|
26123
26311
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26124
26312
|
"button",
|
|
26125
26313
|
{
|
|
@@ -26143,6 +26331,29 @@ var BottlenecksContent = ({
|
|
|
26143
26331
|
)
|
|
26144
26332
|
] })
|
|
26145
26333
|
] }) }),
|
|
26334
|
+
(timestampStart || timestampEnd) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-2 bg-blue-50 border-b border-blue-100 flex items-center justify-between", children: [
|
|
26335
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-2 text-sm text-blue-700", children: [
|
|
26336
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4" }),
|
|
26337
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
26338
|
+
"Filtered by time: ",
|
|
26339
|
+
timestampStart ? formatTimeOnly(timestampStart) : "Any",
|
|
26340
|
+
" - ",
|
|
26341
|
+
timestampEnd ? formatTimeOnly(timestampEnd) : "Any"
|
|
26342
|
+
] })
|
|
26343
|
+
] }),
|
|
26344
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26345
|
+
"button",
|
|
26346
|
+
{
|
|
26347
|
+
onClick: () => {
|
|
26348
|
+
setTimestampStart("");
|
|
26349
|
+
setTimestampEnd("");
|
|
26350
|
+
fetchClips();
|
|
26351
|
+
},
|
|
26352
|
+
className: "text-sm text-blue-600 hover:text-blue-800 transition-colors",
|
|
26353
|
+
children: "Clear filter"
|
|
26354
|
+
}
|
|
26355
|
+
)
|
|
26356
|
+
] }),
|
|
26146
26357
|
isLoading && allVideos.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner2, { size: "md", message: "Loading clips..." }) }) : allVideos.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
|
|
26147
26358
|
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 text-gray-300 mx-auto mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
|
|
26148
26359
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-medium text-gray-700 mb-2", children: "No Clips Found" }),
|
|
@@ -26202,6 +26413,7 @@ var BottlenecksContent = ({
|
|
|
26202
26413
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-80 hidden sm:inline", children: currentVideo.description })
|
|
26203
26414
|
] }) })
|
|
26204
26415
|
),
|
|
26416
|
+
currentVideo.creation_timestamp && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-3 left-3 z-10 bg-black/50 backdrop-blur-sm px-2 py-1 rounded text-white shadow-sm text-xs", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-80", children: formatTimestamp(currentVideo.creation_timestamp) }) }),
|
|
26205
26417
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 z-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-white", children: [
|
|
26206
26418
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26207
26419
|
"button",
|
|
@@ -28750,6 +28962,23 @@ var ThreadSidebar = ({
|
|
|
28750
28962
|
] });
|
|
28751
28963
|
};
|
|
28752
28964
|
var axelProfilePng = "/axel-profile.png";
|
|
28965
|
+
var ProfilePicture = React46__namespace.default.memo(({ alt = "Axel", className = "w-12 h-12" }) => {
|
|
28966
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${className} rounded-xl overflow-hidden shadow-sm`, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
28967
|
+
"img",
|
|
28968
|
+
{
|
|
28969
|
+
src: axelProfilePng,
|
|
28970
|
+
alt,
|
|
28971
|
+
className: "w-full h-full object-cover",
|
|
28972
|
+
loading: "eager",
|
|
28973
|
+
decoding: "async"
|
|
28974
|
+
}
|
|
28975
|
+
) }) });
|
|
28976
|
+
});
|
|
28977
|
+
ProfilePicture.displayName = "ProfilePicture";
|
|
28978
|
+
var preloadImage = (src) => {
|
|
28979
|
+
const img = new Image();
|
|
28980
|
+
img.src = src;
|
|
28981
|
+
};
|
|
28753
28982
|
var AIAgentView = () => {
|
|
28754
28983
|
const { navigate, pathname } = useNavigation();
|
|
28755
28984
|
const config = useDashboardConfig();
|
|
@@ -28768,12 +28997,85 @@ var AIAgentView = () => {
|
|
|
28768
28997
|
const [isTransitioning, setIsTransitioning] = React46.useState(false);
|
|
28769
28998
|
const [typedText, setTypedText] = React46.useState("");
|
|
28770
28999
|
const [newChatCount, setNewChatCount] = React46.useState(0);
|
|
29000
|
+
const [hasStartedTyping, setHasStartedTyping] = React46.useState(false);
|
|
29001
|
+
const [typingStartTime, setTypingStartTime] = React46.useState(null);
|
|
29002
|
+
const [lastTypingTime, setLastTypingTime] = React46.useState(null);
|
|
29003
|
+
const [characterCount, setCharacterCount] = React46.useState(0);
|
|
29004
|
+
const typingTimeoutRef = React46.useRef(null);
|
|
28771
29005
|
const isThreadLoading = (threadId) => {
|
|
28772
29006
|
return threadId ? loadingThreads.has(threadId) : false;
|
|
28773
29007
|
};
|
|
28774
29008
|
const getStreamingState = (threadId) => {
|
|
28775
29009
|
return threadId ? streamingStates.get(threadId) || { message: "", reasoning: "" } : { message: "", reasoning: "" };
|
|
28776
29010
|
};
|
|
29011
|
+
const trackTypingStart = () => {
|
|
29012
|
+
if (!hasStartedTyping) {
|
|
29013
|
+
const now2 = Date.now();
|
|
29014
|
+
setHasStartedTyping(true);
|
|
29015
|
+
setTypingStartTime(now2);
|
|
29016
|
+
setLastTypingTime(now2);
|
|
29017
|
+
trackCoreEvent("AI Agent Input Started", {
|
|
29018
|
+
line_id: lineId,
|
|
29019
|
+
company_id: companyId,
|
|
29020
|
+
shift_id: shiftId,
|
|
29021
|
+
active_thread_id: activeThreadId,
|
|
29022
|
+
has_existing_messages: messages.length > 0,
|
|
29023
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
29024
|
+
});
|
|
29025
|
+
}
|
|
29026
|
+
};
|
|
29027
|
+
const trackTypingProgress = (newValue) => {
|
|
29028
|
+
const now2 = Date.now();
|
|
29029
|
+
setLastTypingTime(now2);
|
|
29030
|
+
setCharacterCount(newValue.length);
|
|
29031
|
+
if (typingTimeoutRef.current) {
|
|
29032
|
+
clearTimeout(typingTimeoutRef.current);
|
|
29033
|
+
}
|
|
29034
|
+
typingTimeoutRef.current = setTimeout(() => {
|
|
29035
|
+
if (hasStartedTyping && typingStartTime && newValue.length > 0) {
|
|
29036
|
+
const typingDuration = now2 - typingStartTime;
|
|
29037
|
+
trackCoreEvent("AI Agent Input Typing Progress", {
|
|
29038
|
+
line_id: lineId,
|
|
29039
|
+
company_id: companyId,
|
|
29040
|
+
shift_id: shiftId,
|
|
29041
|
+
active_thread_id: activeThreadId,
|
|
29042
|
+
character_count: newValue.length,
|
|
29043
|
+
typing_duration_ms: typingDuration,
|
|
29044
|
+
has_existing_messages: messages.length > 0,
|
|
29045
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
29046
|
+
});
|
|
29047
|
+
}
|
|
29048
|
+
}, 2e3);
|
|
29049
|
+
};
|
|
29050
|
+
const trackMessageSent = (messageContent) => {
|
|
29051
|
+
if (hasStartedTyping && typingStartTime) {
|
|
29052
|
+
const now2 = Date.now();
|
|
29053
|
+
const totalTypingDuration = now2 - typingStartTime;
|
|
29054
|
+
trackCoreEvent("AI Agent Message Sent", {
|
|
29055
|
+
line_id: lineId,
|
|
29056
|
+
company_id: companyId,
|
|
29057
|
+
shift_id: shiftId,
|
|
29058
|
+
active_thread_id: activeThreadId,
|
|
29059
|
+
message_length: messageContent.length,
|
|
29060
|
+
character_count: messageContent.length,
|
|
29061
|
+
typing_duration_ms: totalTypingDuration,
|
|
29062
|
+
has_existing_messages: messages.length > 0,
|
|
29063
|
+
is_new_conversation: !activeThreadId,
|
|
29064
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
29065
|
+
});
|
|
29066
|
+
}
|
|
29067
|
+
resetTypingState();
|
|
29068
|
+
};
|
|
29069
|
+
const resetTypingState = () => {
|
|
29070
|
+
setHasStartedTyping(false);
|
|
29071
|
+
setTypingStartTime(null);
|
|
29072
|
+
setLastTypingTime(null);
|
|
29073
|
+
setCharacterCount(0);
|
|
29074
|
+
if (typingTimeoutRef.current) {
|
|
29075
|
+
clearTimeout(typingTimeoutRef.current);
|
|
29076
|
+
typingTimeoutRef.current = null;
|
|
29077
|
+
}
|
|
29078
|
+
};
|
|
28777
29079
|
const textareaRef = React46.useRef(null);
|
|
28778
29080
|
const messagesEndRef = React46.useRef(null);
|
|
28779
29081
|
const containerRef = React46.useRef(null);
|
|
@@ -28796,7 +29098,7 @@ var AIAgentView = () => {
|
|
|
28796
29098
|
const { shiftId } = getCurrentShift(dateTimeConfig.defaultTimezone || "Asia/Kolkata", shiftConfig);
|
|
28797
29099
|
const companyId = entityConfig.companyId || "default-company-id";
|
|
28798
29100
|
const ACTIVE_THREAD_STORAGE_KEY = `ai-agent-active-thread-${lineId}`;
|
|
28799
|
-
React46.
|
|
29101
|
+
React46.useLayoutEffect(() => {
|
|
28800
29102
|
const savedThreadId = localStorage.getItem(ACTIVE_THREAD_STORAGE_KEY);
|
|
28801
29103
|
if (savedThreadId && savedThreadId !== "undefined") {
|
|
28802
29104
|
setActiveThreadId(savedThreadId);
|
|
@@ -28809,6 +29111,27 @@ var AIAgentView = () => {
|
|
|
28809
29111
|
localStorage.removeItem(ACTIVE_THREAD_STORAGE_KEY);
|
|
28810
29112
|
}
|
|
28811
29113
|
}, [activeThreadId, ACTIVE_THREAD_STORAGE_KEY]);
|
|
29114
|
+
React46.useEffect(() => {
|
|
29115
|
+
const handleVisibilityChange = () => {
|
|
29116
|
+
if (document.visibilityState === "hidden" && activeThreadId) {
|
|
29117
|
+
localStorage.setItem(ACTIVE_THREAD_STORAGE_KEY, activeThreadId);
|
|
29118
|
+
}
|
|
29119
|
+
};
|
|
29120
|
+
const handleBeforeUnload = () => {
|
|
29121
|
+
if (activeThreadId) {
|
|
29122
|
+
localStorage.setItem(ACTIVE_THREAD_STORAGE_KEY, activeThreadId);
|
|
29123
|
+
}
|
|
29124
|
+
};
|
|
29125
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
29126
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
29127
|
+
return () => {
|
|
29128
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
29129
|
+
window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
29130
|
+
if (activeThreadId) {
|
|
29131
|
+
localStorage.setItem(ACTIVE_THREAD_STORAGE_KEY, activeThreadId);
|
|
29132
|
+
}
|
|
29133
|
+
};
|
|
29134
|
+
}, [activeThreadId, ACTIVE_THREAD_STORAGE_KEY]);
|
|
28812
29135
|
React46.useEffect(() => {
|
|
28813
29136
|
if (textareaRef.current) {
|
|
28814
29137
|
textareaRef.current.style.height = "auto";
|
|
@@ -28859,9 +29182,20 @@ var AIAgentView = () => {
|
|
|
28859
29182
|
setInputValue("");
|
|
28860
29183
|
setPendingThreadId(null);
|
|
28861
29184
|
setTypedText("");
|
|
29185
|
+
resetTypingState();
|
|
28862
29186
|
localStorage.removeItem(ACTIVE_THREAD_STORAGE_KEY);
|
|
28863
29187
|
textareaRef.current?.focus();
|
|
28864
29188
|
};
|
|
29189
|
+
React46.useEffect(() => {
|
|
29190
|
+
preloadImage(axelProfilePng);
|
|
29191
|
+
}, []);
|
|
29192
|
+
React46.useEffect(() => {
|
|
29193
|
+
return () => {
|
|
29194
|
+
if (typingTimeoutRef.current) {
|
|
29195
|
+
clearTimeout(typingTimeoutRef.current);
|
|
29196
|
+
}
|
|
29197
|
+
};
|
|
29198
|
+
}, []);
|
|
28865
29199
|
React46.useEffect(() => {
|
|
28866
29200
|
const checkAuth = async () => {
|
|
28867
29201
|
const supabase2 = _getSupabaseInstance();
|
|
@@ -28892,6 +29226,7 @@ var AIAgentView = () => {
|
|
|
28892
29226
|
let currentThreadId = activeThreadId || `temp-${Date.now()}`;
|
|
28893
29227
|
if (isThreadLoading(currentThreadId)) return;
|
|
28894
29228
|
const userMessage = inputValue.trim();
|
|
29229
|
+
trackMessageSent(userMessage);
|
|
28895
29230
|
if (displayMessages.length === 0) {
|
|
28896
29231
|
setIsTransitioning(true);
|
|
28897
29232
|
setTimeout(() => {
|
|
@@ -29299,18 +29634,11 @@ var AIAgentView = () => {
|
|
|
29299
29634
|
{
|
|
29300
29635
|
ref: containerRef,
|
|
29301
29636
|
className: `flex-1 bg-gray-50/50 min-h-0 ${displayMessages.length === 0 && !isTransitioning ? "flex items-center justify-center" : "overflow-y-auto"}`,
|
|
29302
|
-
children: displayMessages.length === 0 && !isTransitioning ? (
|
|
29637
|
+
children: !activeThreadId && displayMessages.length === 0 && !isTransitioning ? (
|
|
29303
29638
|
/* Centered welcome and input for new chat */
|
|
29304
29639
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-3xl mx-auto px-4 sm:px-6 flex flex-col items-center justify-center space-y-12 -mt-16", children: [
|
|
29305
29640
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
29306
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center mb-8", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
29307
|
-
"img",
|
|
29308
|
-
{
|
|
29309
|
-
src: axelProfilePng,
|
|
29310
|
-
alt: "Axel - AI Manufacturing Expert",
|
|
29311
|
-
className: "w-full h-full object-cover"
|
|
29312
|
-
}
|
|
29313
|
-
) }) }),
|
|
29641
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center mb-8", children: /* @__PURE__ */ jsxRuntime.jsx(ProfilePicture, { alt: "Axel - AI Manufacturing Expert", className: "w-24 h-24" }) }),
|
|
29314
29642
|
/* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "text-3xl font-semibold text-gray-900", children: [
|
|
29315
29643
|
typedText,
|
|
29316
29644
|
typedText.length < "Hi, I'm Axel - Your AI Supervisor".length && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-pulse", children: "|" })
|
|
@@ -29335,8 +29663,30 @@ var AIAgentView = () => {
|
|
|
29335
29663
|
{
|
|
29336
29664
|
ref: textareaRef,
|
|
29337
29665
|
value: inputValue,
|
|
29338
|
-
onChange: (e) =>
|
|
29666
|
+
onChange: (e) => {
|
|
29667
|
+
const newValue = e.target.value;
|
|
29668
|
+
setInputValue(newValue);
|
|
29669
|
+
if (newValue.length > 0 && !hasStartedTyping) {
|
|
29670
|
+
trackTypingStart();
|
|
29671
|
+
}
|
|
29672
|
+
if (newValue.length > 0) {
|
|
29673
|
+
trackTypingProgress(newValue);
|
|
29674
|
+
}
|
|
29675
|
+
if (newValue.length === 0) {
|
|
29676
|
+
resetTypingState();
|
|
29677
|
+
}
|
|
29678
|
+
},
|
|
29339
29679
|
onKeyDown: handleKeyDown,
|
|
29680
|
+
onFocus: () => {
|
|
29681
|
+
trackCoreEvent("AI Agent Input Focused", {
|
|
29682
|
+
line_id: lineId,
|
|
29683
|
+
company_id: companyId,
|
|
29684
|
+
shift_id: shiftId,
|
|
29685
|
+
active_thread_id: activeThreadId,
|
|
29686
|
+
has_existing_messages: messages.length > 0,
|
|
29687
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
29688
|
+
});
|
|
29689
|
+
},
|
|
29340
29690
|
placeholder: "Ask me about production optimization, quality metrics, or any manufacturing challenge...",
|
|
29341
29691
|
className: "w-full resize-none bg-transparent px-2 py-2 pr-12 focus:outline-none placeholder-gray-500 text-gray-900 text-sm leading-relaxed",
|
|
29342
29692
|
rows: 1,
|
|
@@ -29371,14 +29721,7 @@ var AIAgentView = () => {
|
|
|
29371
29721
|
{
|
|
29372
29722
|
className: `flex gap-4 ${message.role === "user" ? "justify-end" : "justify-start"}`,
|
|
29373
29723
|
children: [
|
|
29374
|
-
message.role === "assistant" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
29375
|
-
"img",
|
|
29376
|
-
{
|
|
29377
|
-
src: axelProfilePng,
|
|
29378
|
-
alt: "Axel",
|
|
29379
|
-
className: "w-full h-full object-cover"
|
|
29380
|
-
}
|
|
29381
|
-
) }) }),
|
|
29724
|
+
message.role === "assistant" && /* @__PURE__ */ jsxRuntime.jsx(ProfilePicture, {}),
|
|
29382
29725
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `max-w-none w-full group ${message.role === "user" ? "order-1" : ""}`, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
29383
29726
|
"div",
|
|
29384
29727
|
{
|
|
@@ -29391,17 +29734,10 @@ var AIAgentView = () => {
|
|
|
29391
29734
|
) })
|
|
29392
29735
|
]
|
|
29393
29736
|
},
|
|
29394
|
-
message.id === -1 ?
|
|
29737
|
+
message.id === -1 ? "streaming-message" : `${message.id}-${index}`
|
|
29395
29738
|
)),
|
|
29396
29739
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 justify-start", children: [
|
|
29397
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29398
|
-
"img",
|
|
29399
|
-
{
|
|
29400
|
-
src: axelProfilePng,
|
|
29401
|
-
alt: "Axel",
|
|
29402
|
-
className: "w-full h-full object-cover"
|
|
29403
|
-
}
|
|
29404
|
-
) }) }),
|
|
29740
|
+
/* @__PURE__ */ jsxRuntime.jsx(ProfilePicture, {}),
|
|
29405
29741
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white border border-gray-200/80 px-5 py-4 rounded-2xl shadow-sm max-w-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
29406
29742
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex space-x-1", children: [
|
|
29407
29743
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-bounce" }),
|
|
@@ -29420,14 +29756,7 @@ var AIAgentView = () => {
|
|
|
29420
29756
|
{
|
|
29421
29757
|
className: `flex gap-4 ${message.role === "user" ? "justify-end" : "justify-start"}`,
|
|
29422
29758
|
children: [
|
|
29423
|
-
message.role === "assistant" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
29424
|
-
"img",
|
|
29425
|
-
{
|
|
29426
|
-
src: axelProfilePng,
|
|
29427
|
-
alt: "Axel",
|
|
29428
|
-
className: "w-full h-full object-cover"
|
|
29429
|
-
}
|
|
29430
|
-
) }) }),
|
|
29759
|
+
message.role === "assistant" && /* @__PURE__ */ jsxRuntime.jsx(ProfilePicture, {}),
|
|
29431
29760
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `max-w-none w-full group ${message.role === "user" ? "order-1" : ""}`, children: [
|
|
29432
29761
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
29433
29762
|
"div",
|
|
@@ -29464,7 +29793,7 @@ var AIAgentView = () => {
|
|
|
29464
29793
|
] })
|
|
29465
29794
|
]
|
|
29466
29795
|
},
|
|
29467
|
-
message.id === -1 ?
|
|
29796
|
+
message.id === -1 ? "streaming-message" : `${message.id}-${index}`
|
|
29468
29797
|
)),
|
|
29469
29798
|
lastError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 justify-start", children: [
|
|
29470
29799
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-xl bg-red-100 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-6 h-6 text-red-600" }) }) }),
|
|
@@ -29481,14 +29810,7 @@ var AIAgentView = () => {
|
|
|
29481
29810
|
] })
|
|
29482
29811
|
] }),
|
|
29483
29812
|
isCurrentThreadLoading && !currentStreaming.message && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 justify-start", children: [
|
|
29484
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29485
|
-
"img",
|
|
29486
|
-
{
|
|
29487
|
-
src: axelProfilePng,
|
|
29488
|
-
alt: "Axel",
|
|
29489
|
-
className: "w-full h-full object-cover"
|
|
29490
|
-
}
|
|
29491
|
-
) }) }),
|
|
29813
|
+
/* @__PURE__ */ jsxRuntime.jsx(ProfilePicture, {}),
|
|
29492
29814
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white border border-gray-200/80 px-5 py-4 rounded-2xl shadow-sm max-w-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
29493
29815
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex space-x-1", children: [
|
|
29494
29816
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-bounce" }),
|
|
@@ -29529,8 +29851,30 @@ var AIAgentView = () => {
|
|
|
29529
29851
|
{
|
|
29530
29852
|
ref: textareaRef,
|
|
29531
29853
|
value: inputValue,
|
|
29532
|
-
onChange: (e) =>
|
|
29854
|
+
onChange: (e) => {
|
|
29855
|
+
const newValue = e.target.value;
|
|
29856
|
+
setInputValue(newValue);
|
|
29857
|
+
if (newValue.length > 0 && !hasStartedTyping) {
|
|
29858
|
+
trackTypingStart();
|
|
29859
|
+
}
|
|
29860
|
+
if (newValue.length > 0) {
|
|
29861
|
+
trackTypingProgress(newValue);
|
|
29862
|
+
}
|
|
29863
|
+
if (newValue.length === 0) {
|
|
29864
|
+
resetTypingState();
|
|
29865
|
+
}
|
|
29866
|
+
},
|
|
29533
29867
|
onKeyDown: handleKeyDown,
|
|
29868
|
+
onFocus: () => {
|
|
29869
|
+
trackCoreEvent("AI Agent Input Focused", {
|
|
29870
|
+
line_id: lineId,
|
|
29871
|
+
company_id: companyId,
|
|
29872
|
+
shift_id: shiftId,
|
|
29873
|
+
active_thread_id: activeThreadId,
|
|
29874
|
+
has_existing_messages: messages.length > 0,
|
|
29875
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
29876
|
+
});
|
|
29877
|
+
},
|
|
29534
29878
|
placeholder: "Ask me about production optimization, quality metrics, or any manufacturing challenge...",
|
|
29535
29879
|
className: "w-full resize-none bg-transparent px-2 py-2 pr-12 focus:outline-none placeholder-gray-500 text-gray-900 text-sm leading-relaxed",
|
|
29536
29880
|
rows: 1,
|
|
@@ -31444,7 +31788,8 @@ var parseBreaksFromDB = (dbBreaks) => {
|
|
|
31444
31788
|
duration: calculateBreakDuration(
|
|
31445
31789
|
breakItem.start || breakItem.startTime || "00:00",
|
|
31446
31790
|
breakItem.end || breakItem.endTime || "00:00"
|
|
31447
|
-
)
|
|
31791
|
+
),
|
|
31792
|
+
remarks: breakItem.remarks || breakItem.name || ""
|
|
31448
31793
|
}));
|
|
31449
31794
|
} else if (dbBreaks.breaks && Array.isArray(dbBreaks.breaks)) {
|
|
31450
31795
|
return dbBreaks.breaks.map((breakItem) => ({
|
|
@@ -31453,7 +31798,8 @@ var parseBreaksFromDB = (dbBreaks) => {
|
|
|
31453
31798
|
duration: calculateBreakDuration(
|
|
31454
31799
|
breakItem.start || breakItem.startTime || "00:00",
|
|
31455
31800
|
breakItem.end || breakItem.endTime || "00:00"
|
|
31456
|
-
)
|
|
31801
|
+
),
|
|
31802
|
+
remarks: breakItem.remarks || breakItem.name || ""
|
|
31457
31803
|
}));
|
|
31458
31804
|
} else {
|
|
31459
31805
|
console.warn("Unexpected breaks format:", dbBreaks);
|
|
@@ -31471,7 +31817,8 @@ var formatBreaks = (breaks) => {
|
|
|
31471
31817
|
return {
|
|
31472
31818
|
breaks: breaks.map((breakItem) => ({
|
|
31473
31819
|
start: breakItem.startTime,
|
|
31474
|
-
end: breakItem.endTime
|
|
31820
|
+
end: breakItem.endTime,
|
|
31821
|
+
remarks: breakItem.remarks || ""
|
|
31475
31822
|
}))
|
|
31476
31823
|
};
|
|
31477
31824
|
};
|
|
@@ -31481,13 +31828,14 @@ var BreakRow = React46.memo(({
|
|
|
31481
31828
|
onRemove,
|
|
31482
31829
|
index
|
|
31483
31830
|
}) => {
|
|
31484
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-
|
|
31831
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-12 gap-2 sm:gap-4 items-center w-full bg-white hover:bg-gray-50 rounded-md transition-all duration-200 p-2", children: [
|
|
31485
31832
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-3", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
31486
31833
|
"input",
|
|
31487
31834
|
{
|
|
31488
31835
|
type: "time",
|
|
31489
31836
|
value: breakItem.startTime,
|
|
31490
31837
|
onChange: (e) => onUpdate(index, "startTime", e.target.value),
|
|
31838
|
+
step: "60",
|
|
31491
31839
|
className: "w-full px-2 sm:px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
31492
31840
|
}
|
|
31493
31841
|
) }),
|
|
@@ -31497,6 +31845,7 @@ var BreakRow = React46.memo(({
|
|
|
31497
31845
|
type: "time",
|
|
31498
31846
|
value: breakItem.endTime,
|
|
31499
31847
|
onChange: (e) => onUpdate(index, "endTime", e.target.value),
|
|
31848
|
+
step: "60",
|
|
31500
31849
|
className: "w-full px-2 sm:px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
31501
31850
|
}
|
|
31502
31851
|
) }),
|
|
@@ -31504,6 +31853,16 @@ var BreakRow = React46.memo(({
|
|
|
31504
31853
|
breakItem.duration,
|
|
31505
31854
|
" min"
|
|
31506
31855
|
] }) }),
|
|
31856
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-3", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
31857
|
+
"input",
|
|
31858
|
+
{
|
|
31859
|
+
type: "text",
|
|
31860
|
+
value: breakItem.remarks || "",
|
|
31861
|
+
onChange: (e) => onUpdate(index, "remarks", e.target.value),
|
|
31862
|
+
placeholder: "Break remarks",
|
|
31863
|
+
className: "w-full px-2 sm:px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
31864
|
+
}
|
|
31865
|
+
) }),
|
|
31507
31866
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
31508
31867
|
"button",
|
|
31509
31868
|
{
|
|
@@ -31620,10 +31979,11 @@ var ShiftPanel = React46.memo(({
|
|
|
31620
31979
|
"Breaks"
|
|
31621
31980
|
] }) }),
|
|
31622
31981
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2 mb-4 w-full", children: breaks.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
31623
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-
|
|
31982
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-12 gap-2 sm:gap-4 text-xs font-medium text-gray-500 mb-1 w-full px-2", children: [
|
|
31624
31983
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-3", children: "Break Start" }),
|
|
31625
31984
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-3", children: "Break End" }),
|
|
31626
31985
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2", children: "Duration" }),
|
|
31986
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-3", children: "Remarks" }),
|
|
31627
31987
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-1" })
|
|
31628
31988
|
] }),
|
|
31629
31989
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-gray-50/80 p-2 rounded-md", children: breaks.map((breakItem, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -31836,7 +32196,8 @@ var ShiftsView = ({
|
|
|
31836
32196
|
const newBreak = {
|
|
31837
32197
|
startTime: dayShift.startTime,
|
|
31838
32198
|
endTime: dayShift.startTime,
|
|
31839
|
-
duration: 0
|
|
32199
|
+
duration: 0,
|
|
32200
|
+
remarks: ""
|
|
31840
32201
|
};
|
|
31841
32202
|
return {
|
|
31842
32203
|
...typedConfig,
|
|
@@ -31877,14 +32238,16 @@ var ShiftsView = ({
|
|
|
31877
32238
|
const dayShift = { ...typedConfig.dayShift };
|
|
31878
32239
|
const newBreaks = [...dayShift.breaks];
|
|
31879
32240
|
newBreaks[index] = { ...newBreaks[index], [field]: value };
|
|
31880
|
-
|
|
31881
|
-
|
|
31882
|
-
|
|
31883
|
-
|
|
31884
|
-
|
|
31885
|
-
endMinutes
|
|
31886
|
-
|
|
31887
|
-
|
|
32241
|
+
if (field === "startTime" || field === "endTime") {
|
|
32242
|
+
const startParts = newBreaks[index].startTime.split(":").map(Number);
|
|
32243
|
+
const endParts = newBreaks[index].endTime.split(":").map(Number);
|
|
32244
|
+
let startMinutes = startParts[0] * 60 + startParts[1];
|
|
32245
|
+
let endMinutes = endParts[0] * 60 + endParts[1];
|
|
32246
|
+
if (endMinutes < startMinutes) {
|
|
32247
|
+
endMinutes += 24 * 60;
|
|
32248
|
+
}
|
|
32249
|
+
newBreaks[index].duration = endMinutes - startMinutes;
|
|
32250
|
+
}
|
|
31888
32251
|
return {
|
|
31889
32252
|
...typedConfig,
|
|
31890
32253
|
dayShift: {
|
|
@@ -31903,14 +32266,16 @@ var ShiftsView = ({
|
|
|
31903
32266
|
const nightShift = { ...typedConfig.nightShift };
|
|
31904
32267
|
const newBreaks = [...nightShift.breaks];
|
|
31905
32268
|
newBreaks[index] = { ...newBreaks[index], [field]: value };
|
|
31906
|
-
|
|
31907
|
-
|
|
31908
|
-
|
|
31909
|
-
|
|
31910
|
-
|
|
31911
|
-
endMinutes
|
|
31912
|
-
|
|
31913
|
-
|
|
32269
|
+
if (field === "startTime" || field === "endTime") {
|
|
32270
|
+
const startParts = newBreaks[index].startTime.split(":").map(Number);
|
|
32271
|
+
const endParts = newBreaks[index].endTime.split(":").map(Number);
|
|
32272
|
+
let startMinutes = startParts[0] * 60 + startParts[1];
|
|
32273
|
+
let endMinutes = endParts[0] * 60 + endParts[1];
|
|
32274
|
+
if (endMinutes < startMinutes) {
|
|
32275
|
+
endMinutes += 24 * 60;
|
|
32276
|
+
}
|
|
32277
|
+
newBreaks[index].duration = endMinutes - startMinutes;
|
|
32278
|
+
}
|
|
31914
32279
|
return {
|
|
31915
32280
|
...typedConfig,
|
|
31916
32281
|
nightShift: {
|