From 3f9f6c86f414656f1d269ca7cb15d05bea98166b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 3 Dec 2025 11:48:44 +0000 Subject: [PATCH] Redesign job details panel as slide-in sheet for better MES UX Convert the operation details modal from a Dialog to a right-side Sheet panel with compact, MES-optimized layout: - Use Sheet component sliding from right (600px width) - Fixed header with operation name, job/part info, and cell badge - Time info bar (estimated/actual/remaining) always visible at top - Scrollable content area for all details - Fixed footer with action buttons (start/stop timing, complete, report issue) - Compact spacing following design system guidelines - Glass card styling with backdrop blur - Proper overflow handling - no more content spilling outside visible area --- .../operator/OperationDetailModal.tsx | 607 ++++++++---------- 1 file changed, 280 insertions(+), 327 deletions(-) diff --git a/src/components/operator/OperationDetailModal.tsx b/src/components/operator/OperationDetailModal.tsx index 4470e65d..9ca03e03 100644 --- a/src/components/operator/OperationDetailModal.tsx +++ b/src/components/operator/OperationDetailModal.tsx @@ -6,6 +6,12 @@ import { completeOperation, } from "@/lib/database"; import { useAuth } from "@/contexts/AuthContext"; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; import { Dialog, DialogContent, @@ -14,7 +20,6 @@ import { } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { Separator } from "@/components/ui/separator"; import { AlertDialog, AlertDialogAction, @@ -37,11 +42,11 @@ import { FileText, Eye, Wrench, + X, } from "lucide-react"; import { format } from "date-fns"; import { toast } from "sonner"; import { supabase } from "@/integrations/supabase/client"; -import MetadataDisplay from "@/components/ui/MetadataDisplay"; import { EnhancedMetadataDisplay } from "@/components/ui/EnhancedMetadataDisplay"; import IssueForm from "./IssueForm"; import { STEPViewer } from "@/components/STEPViewer"; @@ -257,388 +262,336 @@ export default function OperationDetailModal({ const isOvertime = remainingTime < 0; return ( - - - - - {operation.operation_name} - - - -
- {/* Job & Part Info */} -
-
-
- {t("operations.job")} -
-
- {operation.part.job.job_number} -
- {operation.part.job.customer && ( -
- {operation.part.job.customer} + + + {/* Fixed Header */} +
+ +
+
+ + {operation.operation_name} + +
+ {t("operations.job")} {operation.part.job.job_number} + + {operation.part.part_number}
- )} -
-
-
- {t("operations.part")} -
-
{operation.part.part_number}
-
- {operation.part.material} • {t("operations.qty")}:{" "} - {operation.part.quantity} -
-
-
- - - - {/* Cell & Status */} -
-
-
- {t("operations.cell")}
{operation.cell.name}
-
-
- {t("operations.statusLabel")} -
- - {operation.status.replace("_", " ")} - -
-
- - {/* Time Info */} -
-
-
- {t("operations.estimated")} -
-
- + + + {/* Time Info Bar */} +
+
+
{t("operations.estimated")}
+
+ {operation.estimated_time}m
-
-
- {t("operations.actual")} -
-
- {operation.actual_time || 0}m -
+
+
{t("operations.actual")}
+
{operation.actual_time || 0}m
-
-
- {t("operations.remaining")} -
-
- {isOvertime ? "+" : ""} - {Math.abs(remainingTime)}m +
+
{t("operations.remaining")}
+
+ {isOvertime ? "+" : ""}{Math.abs(remainingTime)}m
+
- {/* Due Date */} - {dueDate && ( -
-
- {t("operations.dueDate")} + {/* Scrollable Content */} +
+
+ {/* Job & Part Details */} +
+
+
{t("operations.part")}
+
{operation.part.part_number}
+
+ {operation.part.material} • {t("operations.qty")}: {operation.part.quantity} +
-
- {format(new Date(dueDate), "PPP")} +
+
{t("operations.statusLabel")}
+ + {operation.status.replace("_", " ")} + + {dueDate && ( +
+ {t("operations.dueDate")}: {format(new Date(dueDate), "MMM d, yyyy")} +
+ )}
- )} - {/* Assembly Warning */} - {operation.part.parent_part_id && ( -
- -
-
- {t("operations.assemblyPart")} -
-
- {t("operations.assemblyWarning")} -
+ {operation.part.job.customer && ( +
+ {operation.part.job.customer}
-
- )} + )} - {/* Active Operator */} - {operation.active_time_entry && !isCurrentUserTiming && ( -
- -
- - {operation.active_time_entry.operator.full_name} - {" "} - {t("operations.currentlyWorking")} + {/* Assembly Warning */} + {operation.part.parent_part_id && ( +
+ +
+
+ {t("operations.assemblyPart")} +
+
+ {t("operations.assemblyWarning")} +
+
-
- )} + )} - {/* Notes */} - {operation.notes && ( -
-
- {t("operations.notes")} -
-
- {operation.notes} + {/* Active Operator */} + {operation.active_time_entry && !isCurrentUserTiming && ( +
+ +
+ + {operation.active_time_entry.operator.full_name} + {" "} + {t("operations.currentlyWorking")} +
-
- )} + )} - {/* Operation Metadata (Process-specific settings) */} - {(operation as any).metadata && ( - - )} + {/* Notes */} + {operation.notes && ( +
+
{t("operations.notes")}
+
{operation.notes}
+
+ )} - {/* Part Metadata */} - {(operation.part as any).metadata && ( - - )} + {/* Operation Metadata (Process-specific settings) */} + {(operation as any).metadata && ( + + )} - {/* Files Section */} - {operation.part.file_paths && - operation.part.file_paths.length > 0 && ( + {/* Required Resources Section */} + {requiredResources.length > 0 && (
-
- {t("operations.files")} +
+ + {t("operations.requiredResources")}
- {operation.part.file_paths.map( - (filePath: string, index: number) => { - const fileName = filePath.split("/").pop() || "Unknown"; - const fileExt = filePath.split(".").pop()?.toLowerCase(); - const isSTEP = fileExt === "step" || fileExt === "stp"; - const isPDF = fileExt === "pdf"; - - if (!isSTEP && !isPDF) return null; - - return ( -
-
- {isSTEP ? ( - - ) : ( - + {requiredResources.map((opResource: any) => ( +
+
+
+
+

+ {opResource.resource.name} +

+ {opResource.quantity > 1 && ( + + ×{opResource.quantity} + + )} +
+
+

{t("operations.type")}: {opResource.resource.type.replace("_", " ")}

+ {opResource.resource.identifier && ( +

ID: {opResource.resource.identifier}

+ )} + {opResource.resource.location && ( +

{t("operations.location")}: {opResource.resource.location}

)} -
-

{fileName}

-

- {isSTEP - ? t("operations.3dModel") - : t("operations.drawing")} -

-
-
- ); - }, - )} -
-
- )} + + {opResource.resource.status.replace("_", " ")} + +
- {/* Required Resources Section */} - {requiredResources.length > 0 && ( -
-
- - {t("operations.requiredResources")} -
-
- {requiredResources.map((opResource: any) => ( -
-
-
-
- -

- {opResource.resource.name} -

- {opResource.quantity > 1 && ( - - {t("operations.qty")}: {opResource.quantity} - - )} -
-
-

- {t("operations.type")}:{" "} - {opResource.resource.type.replace("_", " ")} -

- {opResource.resource.identifier && ( -

- ID: {opResource.resource.identifier} -

- )} - {opResource.resource.location && ( -

- {t("operations.location")}:{" "} - {opResource.resource.location} + {opResource.notes && ( +

+
+ +

+ {opResource.notes}

- )} +
-
- - {opResource.resource.status.replace("_", " ")} - + )} + + {opResource.resource.metadata && ( +
+ +
+ )}
+ ))} +
+
+ )} - {/* Resource-specific instructions */} - {opResource.notes && ( -
-
- -
-

- Instructions: -

-

- {opResource.notes} + {/* Files Section */} + {operation.part.file_paths && operation.part.file_paths.length > 0 && ( +

+
+ {t("operations.files")} +
+
+ {operation.part.file_paths.map((filePath: string, index: number) => { + const fileName = filePath.split("/").pop() || "Unknown"; + const fileExt = filePath.split(".").pop()?.toLowerCase(); + const isSTEP = fileExt === "step" || fileExt === "stp"; + const isPDF = fileExt === "pdf"; + + if (!isSTEP && !isPDF) return null; + + return ( +
+
+ {isSTEP ? ( + + ) : ( + + )} +
+

{fileName}

+

+ {isSTEP ? t("operations.3dModel") : t("operations.drawing")}

+
- )} - - {/* Resource metadata */} - {opResource.resource.metadata && ( -
- -
- )} -
- ))} + ); + })} +
-
+ )} + + {/* Substeps Section */} + +
+
+ + {/* Fixed Footer with Actions */} +
+ {canReportIssue && ( + )} - +
+ {canStartTiming && ( + + )} - {/* Action Buttons */} -
- {canReportIssue && ( + {isCurrentUserTiming && ( )} -
- {canStartTiming && ( - - )} - - {isCurrentUserTiming && ( - - )} - - {canComplete && ( - - )} -
+ {canComplete && ( + + )}
- {/* Substeps Section */} -
- -
+
- +
- + ); }