@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.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
- if (includeCycleTime && (parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time") || parsedInfo.type === "best_cycle_time" || parsedInfo.type === "worst_cycle_time")) {
25360
- cycleTimeSeconds = await this.getMetadataCycleTime(uri);
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
- const videos = videoResults.filter((v) => v !== null);
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: date || getOperationalDate(),
25693
+ date: operationalDate,
25615
25694
  mode: "full",
25616
25695
  includeCycleTime: true,
25617
- limit: 50
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.useEffect(() => {
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("div", { className: "w-24 h-24 rounded-xl overflow-hidden shadow-sm", 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) => setInputValue(e.target.value),
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("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__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 ? `streaming-${currentStreaming.message.length}` : `${message.id}-${index}`
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("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__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("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__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 ? `streaming-${currentStreaming.message.length}` : `${message.id}-${index}`
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("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 rounded-xl overflow-hidden shadow-sm", children: /* @__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) => setInputValue(e.target.value),
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-9 gap-2 sm:gap-4 items-center w-full bg-white hover:bg-gray-50 rounded-md transition-all duration-200 p-2", children: [
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-9 gap-2 sm:gap-4 text-xs font-medium text-gray-500 mb-1 w-full px-2", children: [
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
- const startParts = newBreaks[index].startTime.split(":").map(Number);
31881
- const endParts = newBreaks[index].endTime.split(":").map(Number);
31882
- let startMinutes = startParts[0] * 60 + startParts[1];
31883
- let endMinutes = endParts[0] * 60 + endParts[1];
31884
- if (endMinutes < startMinutes) {
31885
- endMinutes += 24 * 60;
31886
- }
31887
- newBreaks[index].duration = endMinutes - startMinutes;
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
- const startParts = newBreaks[index].startTime.split(":").map(Number);
31907
- const endParts = newBreaks[index].endTime.split(":").map(Number);
31908
- let startMinutes = startParts[0] * 60 + startParts[1];
31909
- let endMinutes = endParts[0] * 60 + endParts[1];
31910
- if (endMinutes < startMinutes) {
31911
- endMinutes += 24 * 60;
31912
- }
31913
- newBreaks[index].duration = endMinutes - startMinutes;
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: {