Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions mcp-server/src/tools/parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ const tools: Tool[] = [
type: "string",
description: "New current stage ID",
},
drawing_no: {
type: "string",
description: "Drawing number reference",
},
cnc_program_name: {
type: "string",
description: "CNC program name for machine operators (generates QR code)",
},
is_bullet_card: {
type: "boolean",
description: "QRM bullet card flag - indicates rush/priority order",
},
},
required: ["id"],
},
Expand Down
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"lucide-react": "^0.462.0",
"next-themes": "^0.3.0",
"papaparse": "^5.5.3",
"qrcode.react": "^4.2.0",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
Expand Down
150 changes: 148 additions & 2 deletions src/components/admin/PartDetailModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import { useState } from "react";
import { Plus, Save, X, Upload, Eye, Trash2, Box, FileText, AlertTriangle, Package, ChevronRight, Wrench, Image as ImageIcon } from "lucide-react";
import { Switch } from "@/components/ui/switch";
import { useState, useEffect } from "react";
import { Plus, Save, X, Upload, Eye, Trash2, Box, FileText, AlertTriangle, Package, ChevronRight, Wrench, Image as ImageIcon, Zap, QrCode } from "lucide-react";
import { QRCodeSVG } from "qrcode.react";
import { useToast } from "@/hooks/use-toast";
import { STEPViewer } from "@/components/STEPViewer";
import { PDFViewer } from "@/components/PDFViewer";
Expand Down Expand Up @@ -64,6 +66,12 @@ export default function PartDetailModal({ partId, onClose, onUpdate }: PartDetai
const [currentFileType, setCurrentFileType] = useState<'step' | 'pdf' | null>(null);
const [currentFileTitle, setCurrentFileTitle] = useState<string>("");

// New part fields state
const [drawingNo, setDrawingNo] = useState<string>("");
const [cncProgramName, setCncProgramName] = useState<string>("");
const [isBulletCard, setIsBulletCard] = useState<boolean>(false);
const [hasChanges, setHasChanges] = useState<boolean>(false);

// Upload hook with progress tracking and quota validation
const {
progress: uploadProgress,
Expand Down Expand Up @@ -103,6 +111,55 @@ export default function PartDetailModal({ partId, onClose, onUpdate }: PartDetai
},
});

// Initialize new fields from part data
useEffect(() => {
if (part) {
setDrawingNo(part.drawing_no || "");
setCncProgramName(part.cnc_program_name || "");
setIsBulletCard(part.is_bullet_card || false);
setHasChanges(false);
}
}, [part]);

// Mutation to update part fields
const updatePartFieldsMutation = useMutation({
mutationFn: async () => {
const { error } = await supabase
.from("parts")
.update({
drawing_no: drawingNo || null,
cnc_program_name: cncProgramName || null,
is_bullet_card: isBulletCard,
updated_at: new Date().toISOString(),
})
.eq("id", partId);

if (error) throw error;
},
onSuccess: async () => {
toast({
title: t("common.success"),
description: t("parts.fieldsUpdated"),
});
setHasChanges(false);
await queryClient.invalidateQueries({ queryKey: ["part-detail", partId] });
onUpdate();
},
onError: (error: any) => {
toast({
title: t("common.error"),
description: error.message,
variant: "destructive",
});
},
});

// Track changes
const handleFieldChange = (setter: (value: any) => void, value: any) => {
setter(value);
setHasChanges(true);
};

// Fetch available resources for linking
const { data: availableResources } = useQuery({
queryKey: ["available-resources", profile?.tenant_id],
Expand Down Expand Up @@ -500,6 +557,95 @@ export default function PartDetailModal({ partId, onClose, onUpdate }: PartDetai
</div>
</div>

{/* Manufacturing Fields - Drawing No, CNC Program, Bullet Card */}
<div className="border rounded-lg p-4 bg-muted/30">
<div className="flex items-center justify-between mb-4">
<Label className="text-lg flex items-center gap-2">
<QrCode className="h-5 w-5" />
{t("parts.manufacturingInfo")}
</Label>
{hasChanges && (
<Button
size="sm"
onClick={() => updatePartFieldsMutation.mutate()}
disabled={updatePartFieldsMutation.isPending}
>
<Save className="h-4 w-4 mr-2" />
{updatePartFieldsMutation.isPending ? t("common.saving") : t("common.saveChanges")}
</Button>
)}
</div>

<div className="grid grid-cols-2 gap-4">
{/* Drawing Number */}
<div>
<Label htmlFor="drawing-no">{t("parts.drawingNo")}</Label>
<Input
id="drawing-no"
value={drawingNo}
onChange={(e) => handleFieldChange(setDrawingNo, e.target.value)}
placeholder={t("parts.drawingNoPlaceholder")}
className="mt-1"
/>
</div>

{/* CNC Program Name */}
<div>
<Label htmlFor="cnc-program">{t("parts.cncProgramName")}</Label>
<Input
id="cnc-program"
value={cncProgramName}
onChange={(e) => handleFieldChange(setCncProgramName, e.target.value)}
placeholder={t("parts.cncProgramPlaceholder")}
className="mt-1"
/>
</div>

{/* Bullet Card Toggle */}
<div className="col-span-2">
<div className="flex items-center justify-between p-3 border rounded-md bg-card">
<div className="flex items-center gap-3">
<Zap className={`h-5 w-5 ${isBulletCard ? 'text-destructive' : 'text-muted-foreground'}`} />
<div>
<Label htmlFor="bullet-card" className="cursor-pointer">
{t("parts.bulletCard")}
</Label>
<p className="text-xs text-muted-foreground">
{t("parts.bulletCardDesc")}
</p>
</div>
</div>
<Switch
id="bullet-card"
checked={isBulletCard}
onCheckedChange={(checked) => handleFieldChange(setIsBulletCard, checked)}
/>
</div>
</div>

{/* QR Code Preview */}
{cncProgramName && (
<div className="col-span-2">
<Label>{t("parts.qrCodePreview")}</Label>
<div className="mt-2 flex items-center gap-4 p-3 border rounded-md bg-white">
<QRCodeSVG
value={cncProgramName}
size={80}
level="M"
includeMargin={false}
/>
<div>
<p className="font-mono font-bold text-foreground">{cncProgramName}</p>
<p className="text-xs text-muted-foreground">
{t("parts.qrCodeDesc")}
</p>
</div>
</div>
</div>
)}
</div>
</div>

{/* Routing Visualization */}
<div>
<Label className="text-lg">{t("qrm.routing")}</Label>
Expand Down
28 changes: 28 additions & 0 deletions src/components/terminal/CncProgramQrCode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { QRCodeSVG } from 'qrcode.react';
import { cn } from '@/lib/utils';

interface CncProgramQrCodeProps {
programName: string;
className?: string;
size?: number;
}

export function CncProgramQrCode({
programName,
className,
size = 64,
}: CncProgramQrCodeProps) {
if (!programName) return null;

return (
<QRCodeSVG
value={programName}
size={size}
level="M"
includeMargin={false}
bgColor="#ffffff"
fgColor="#000000"
className={cn(className)}
/>
);
}
36 changes: 32 additions & 4 deletions src/components/terminal/DetailPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import React, { useState, useMemo, useEffect } from 'react';
import { TerminalJob } from '@/types/terminal';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Play, Pause, Square, FileText, Box, AlertTriangle, CheckCircle2, Clock, Circle, Maximize2, X, ChevronDown, ChevronRight, PackageCheck } from 'lucide-react';
import { STEPViewer } from '@/components/STEPViewer'; // Reusing existing viewer
import { Play, Pause, Square, FileText, Box, AlertTriangle, CheckCircle2, Clock, Circle, Maximize2, X, ChevronDown, ChevronRight, PackageCheck, Zap } from 'lucide-react';
import { CncProgramQrCode } from './CncProgramQrCode';
import { STEPViewer } from '@/components/STEPViewer';
import { PDFViewer } from '@/components/PDFViewer';
import { OperationWithDetails } from '@/lib/database';
import { cn } from '@/lib/utils';
Expand All @@ -18,6 +17,7 @@ import { useAuth } from '@/contexts/AuthContext';
import { createPortal } from 'react-dom';
import { supabase } from '@/integrations/supabase/client';
import { IconDisplay } from '@/components/ui/icon-picker';
import { useTranslation } from 'react-i18next';

interface Substep {
id: string;
Expand All @@ -41,6 +41,7 @@ interface DetailPanelProps {
}

export function DetailPanel({ job, onStart, onPause, onComplete, stepUrl, pdfUrl, operations = [], onDataRefresh }: DetailPanelProps) {
const { t } = useTranslation();
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
const [isQuantityModalOpen, setIsQuantityModalOpen] = useState(false);
const [fullscreenViewer, setFullscreenViewer] = useState<'3d' | 'pdf' | null>(null);
Expand Down Expand Up @@ -122,10 +123,21 @@ export function DetailPanel({ job, onStart, onPause, onComplete, stepUrl, pdfUrl

{/* Header Card - Compact */}
<div className="p-3 border-b border-border bg-card">
{/* Bullet Card Alert Banner */}
{job.isBulletCard && (
<div className="mb-2 -mx-3 -mt-3 px-3 py-1.5 bg-destructive/10 border-b border-destructive/30 flex items-center gap-2">
<Zap className="w-4 h-4 text-destructive animate-pulse" />
<span className="text-xs font-bold text-destructive uppercase tracking-wider">{t('terminal.bulletCard')}</span>
</div>
)}

<div className="flex justify-between items-start mb-1.5">
<div className="min-w-0 flex-1">
<h2 className="text-lg font-bold text-foreground truncate">{job.jobCode}</h2>
<p className="text-muted-foreground text-xs truncate">{job.description}</p>
{job.drawingNo && (
<p className="text-muted-foreground text-[10px]">{t('parts.drawingNo')}: {job.drawingNo}</p>
)}
</div>
<div className="text-right shrink-0 ml-2">
<div className="text-base font-mono font-bold text-primary">{job.quantity} <span className="text-xs text-muted-foreground">pcs</span></div>
Expand Down Expand Up @@ -206,6 +218,22 @@ export function DetailPanel({ job, onStart, onPause, onComplete, stepUrl, pdfUrl
)}
</div>

{/* CNC Program QR Code - Compact inline display */}
{job.cncProgramName && (
<div className="px-3 py-2 border-b border-border flex items-center gap-3">
<div className="bg-white p-1.5 rounded border border-border shrink-0">
<CncProgramQrCode
programName={job.cncProgramName}
size={56}
/>
</div>
<div className="min-w-0">
<div className="text-[10px] text-muted-foreground uppercase tracking-wider">{t('parts.cncProgramName')}</div>
<p className="font-mono font-semibold text-sm text-foreground truncate">{job.cncProgramName}</p>
</div>
</div>
)}

{/* Main Content Tabs - Compact */}
<div className="flex-1 min-h-0">
<Tabs defaultValue={job.hasModel ? "3d" : job.hasPdf ? "pdf" : "ops"} className="h-full flex flex-col">
Expand Down
6 changes: 5 additions & 1 deletion src/components/terminal/JobRow.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { TerminalJob } from '@/types/terminal';
import { Badge } from '@/components/ui/badge';
import { FileText, Box, AlertTriangle, Clock, User } from 'lucide-react';
import { FileText, Box, AlertTriangle, Clock, User, Zap } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useTranslation } from 'react-i18next';

Expand Down Expand Up @@ -34,11 +34,15 @@ export function JobRow({ job, isSelected, onClick, variant }: JobRowProps) {
isSelected && "bg-accent/50 ring-1 ring-primary",
variant === 'process' && "bg-status-active/5",
job.isCurrentUserClocked && "bg-primary/10 ring-1 ring-primary/50",
job.isBulletCard && "bg-destructive/5 border-l-2 border-l-destructive",
)}
>
{/* Job Number with clocking indicator */}
<td className="px-2 py-1.5 text-sm font-medium text-foreground whitespace-nowrap">
<div className="flex items-center gap-2">
{job.isBulletCard && (
<Zap className="w-3.5 h-3.5 text-destructive shrink-0" title={t('terminal.bulletCard')} />
)}
{job.isCurrentUserClocked && (
<Badge
className="bg-primary text-primary-foreground text-[10px] font-bold px-1.5 py-0 animate-pulse"
Expand Down
18 changes: 16 additions & 2 deletions src/i18n/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,17 @@
"downloadFailedDescription": "Bild kann nicht heruntergeladen werden. Bitte versuchen Sie es erneut.",
"storageUsed": "Speicher verwendet",
"addFailed": "Hinzufügen von Bildern fehlgeschlagen"
}
},
"manufacturingInfo": "Fertigungsinformationen",
"drawingNo": "Zeichnungsnummer",
"drawingNoPlaceholder": "z.B. ZNR-2024-001",
"cncProgramName": "CNC-Programmname",
"cncProgramPlaceholder": "z.B. PROG_12345",
"bulletCard": "Bullet Card (Eilauftrag)",
"bulletCardDesc": "Dieses Teil als Prioritäts-Eilauftrag markieren (QRM-Terminologie)",
"qrCodePreview": "QR-Code Vorschau",
"qrCodeDesc": "Scannen Sie diesen QR-Code an der CNC-Maschine um das Programm zu laden",
"fieldsUpdated": "Teilefelder erfolgreich aktualisiert"
},
"operations": {
"title": "Arbeitsgänge",
Expand Down Expand Up @@ -1332,7 +1342,11 @@
"inBuffer": "Im Puffer",
"expected": "Erwartet",
"noActiveJobs": "Keine aktiven Aufträge",
"jobsFound": "Aufträge gefunden"
"jobsFound": "Aufträge gefunden",
"you": "SIE",
"other": "Andere",
"youAreClockedOn": "Sie sind derzeit bei diesem Arbeitsgang eingestempelt",
"bulletCard": "Eilauftrag - Bullet Card"
},
"activityMonitor": {
"title": "Aktivitätsmonitor",
Expand Down
Loading
Loading