diff --git a/FrontEnd/communication.ts b/FrontEnd/communication.ts index d4e5363..1356cb4 100644 --- a/FrontEnd/communication.ts +++ b/FrontEnd/communication.ts @@ -284,6 +284,65 @@ class CommunicationLayer { }); } } + + public async getRecipe(recipeTitle: string): Promise { + if (isWebView && machine) { + return await machine.GetRecipe(recipeTitle); + } else { + return new Promise((resolve, reject) => { + if (!this.isConnected) { + setTimeout(() => { + if (!this.isConnected) reject("WebSocket connection timeout"); + }, 2000); + } + + const timeoutId = setTimeout(() => { + this.listeners = this.listeners.filter(cb => cb !== handler); + reject("Recipe fetch timeout"); + }, 10000); + + const handler = (data: any) => { + if (data.type === 'RECIPE_DATA') { + clearTimeout(timeoutId); + this.listeners = this.listeners.filter(cb => cb !== handler); + resolve(JSON.stringify(data.data)); + } + }; + this.listeners.push(handler); + this.ws?.send(JSON.stringify({ type: 'GET_RECIPE', recipeTitle })); + }); + } + } + + public async saveRecipe(recipeTitle: string, recipeData: string): Promise<{ success: boolean; message: string; recipeId?: string }> { + if (isWebView && machine) { + const resultJson = await machine.SaveRecipe(recipeTitle, recipeData); + return JSON.parse(resultJson); + } else { + return new Promise((resolve, reject) => { + if (!this.isConnected) { + setTimeout(() => { + if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" }); + }, 2000); + } + + const timeoutId = setTimeout(() => { + this.listeners = this.listeners.filter(cb => cb !== handler); + reject({ success: false, message: "Recipe save timeout" }); + }, 10000); + + const handler = (data: any) => { + if (data.type === 'RECIPE_SAVED') { + clearTimeout(timeoutId); + this.listeners = this.listeners.filter(cb => cb !== handler); + resolve(data.data); + } + }; + this.listeners.push(handler); + this.ws?.send(JSON.stringify({ type: 'SAVE_RECIPE', recipeTitle, recipeData })); + }); + } + } } export const comms = new CommunicationLayer(); diff --git a/FrontEnd/pages/RecipePage.tsx b/FrontEnd/pages/RecipePage.tsx index 377565c..9f4765c 100644 --- a/FrontEnd/pages/RecipePage.tsx +++ b/FrontEnd/pages/RecipePage.tsx @@ -1,62 +1,118 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Layers, Plus, Trash2, Copy, Save, ArrowLeft, FileText, Calendar } from 'lucide-react'; +import { Layers, Plus, Trash2, Copy, Save, ArrowLeft, FileText, Calendar, Check } from 'lucide-react'; import { Recipe } from '../types'; import { comms } from '../communication'; import { TechButton } from '../components/common/TechButton'; +interface RecipeData { + idx?: number; + Title?: string; + Motion?: string; + BCD_1D?: boolean; + BCD_QR?: boolean; + BCD_DM?: boolean; + vOption?: number; + vWMSInfo?: number; + vSIDInfo?: number; + vJobInfo?: number; + vSIDConv?: number; + Def_VName?: string; + Def_MFG?: string; + IgnoreOtherBarcode?: boolean; + DisableCamera?: boolean; + DisablePrinter?: boolean; + CheckSIDExsit?: boolean; + bOwnZPL?: boolean; + IgnorePartNo?: boolean; + IgnoreBatch?: boolean; + BSave?: number; + AutoOutConveyor?: number; + [key: string]: any; +} + export const RecipePage: React.FC = () => { const navigate = useNavigate(); const [recipes, setRecipes] = useState([]); - const [selectedId, setSelectedId] = useState(null); - const [editedRecipe, setEditedRecipe] = useState(null); + const [selectedTitle, setSelectedTitle] = useState(null); + const [editedRecipe, setEditedRecipe] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [hasChanges, setHasChanges] = useState(false); useEffect(() => { - const loadRecipes = async () => { - try { - const recipeStr = await comms.getRecipeList(); - const recipeData = JSON.parse(recipeStr); - setRecipes(recipeData); - if (recipeData.length > 0) { - setSelectedId(recipeData[0].id); - setEditedRecipe(recipeData[0]); - } - } catch (e) { - console.error("Failed to load recipes", e); - } - setIsLoading(false); - }; loadRecipes(); }, []); - const handleSelect = (r: Recipe) => { - setSelectedId(r.id); - setEditedRecipe({ ...r }); + const loadRecipes = async () => { + try { + const recipeStr = await comms.getRecipeList(); + const recipeData = JSON.parse(recipeStr); + setRecipes(recipeData); + if (recipeData.length > 0 && !selectedTitle) { + await handleSelect(recipeData[0]); + } + } catch (e) { + console.error("Failed to load recipes", e); + } + setIsLoading(false); + }; + + const handleSelect = async (r: Recipe) => { + if (hasChanges) { + const confirmed = window.confirm("You have unsaved changes. Do you want to discard them?"); + if (!confirmed) return; + } + + setSelectedTitle(r.name); + try { + const recipeStr = await comms.getRecipe(r.name); + const recipeData = JSON.parse(recipeStr); + setEditedRecipe(recipeData); + setHasChanges(false); + } catch (e) { + console.error("Failed to load recipe details", e); + alert("Failed to load recipe details"); + } + }; + + const handleFieldChange = (field: string, value: any) => { + if (!editedRecipe) return; + setEditedRecipe({ ...editedRecipe, [field]: value }); + setHasChanges(true); }; const handleSave = async () => { - // Mock Save Logic - console.log("Saving recipe:", editedRecipe); - // In real app: await comms.saveRecipe(editedRecipe); + if (!editedRecipe || !selectedTitle) return; + + try { + const result = await comms.saveRecipe(selectedTitle, JSON.stringify(editedRecipe)); + if (result.success) { + setHasChanges(false); + alert("Recipe saved successfully!"); + await loadRecipes(); + } else { + alert(`Failed to save recipe: ${result.message}`); + } + } catch (error: any) { + alert(`Error saving recipe: ${error.message || 'Unknown error'}`); + console.error('Recipe save error:', error); + } }; const handleCopy = async () => { - if (!selectedId) return; + if (!selectedTitle) return; - const selectedRecipe = recipes.find(r => r.id === selectedId); + const selectedRecipe = recipes.find(r => r.name === selectedTitle); if (!selectedRecipe) return; const newName = prompt(`Copy "${selectedRecipe.name}" as:`, `${selectedRecipe.name}_Copy`); if (!newName || newName.trim() === '') return; try { - const result = await comms.copyRecipe(selectedId, newName.trim()); + const result = await comms.copyRecipe(selectedTitle, newName.trim()); if (result.success && result.newRecipe) { - const newRecipeList = [...recipes, result.newRecipe]; - setRecipes(newRecipeList); - setSelectedId(result.newRecipe.id); - setEditedRecipe(result.newRecipe); + await loadRecipes(); + await handleSelect(result.newRecipe); console.log("Recipe copied successfully:", result.newRecipe); } else { alert(`Failed to copy recipe: ${result.message}`); @@ -68,26 +124,25 @@ export const RecipePage: React.FC = () => { }; const handleDelete = async () => { - if (!selectedId) return; + if (!selectedTitle) return; - const selectedRecipe = recipes.find(r => r.id === selectedId); + const selectedRecipe = recipes.find(r => r.name === selectedTitle); if (!selectedRecipe) return; const confirmed = window.confirm(`Are you sure you want to delete "${selectedRecipe.name}"?`); if (!confirmed) return; try { - const result = await comms.deleteRecipe(selectedId); + const result = await comms.deleteRecipe(selectedTitle); if (result.success) { - const newRecipeList = recipes.filter(r => r.id !== selectedId); - setRecipes(newRecipeList); + await loadRecipes(); // Select first recipe or clear selection + const newRecipeList = recipes.filter(r => r.name !== selectedTitle); if (newRecipeList.length > 0) { - setSelectedId(newRecipeList[0].id); - setEditedRecipe(newRecipeList[0]); + await handleSelect(newRecipeList[0]); } else { - setSelectedId(null); + setSelectedTitle(null); setEditedRecipe(null); } @@ -135,7 +190,7 @@ export const RecipePage: React.FC = () => { onClick={() => handleSelect(r)} className={` p-3 rounded border cursor-pointer transition-all group - ${selectedId === r.id + ${selectedTitle === r.name ? 'bg-neon-blue/20 border-neon-blue text-white shadow-glow-blue' : 'bg-white/5 border-white/5 text-slate-400 hover:bg-white/10 hover:border-white/20'} `} @@ -149,16 +204,13 @@ export const RecipePage: React.FC = () => { {/* List Actions */} -
- - - +
@@ -167,7 +219,7 @@ export const RecipePage: React.FC = () => { className="flex justify-center" title="Delete Selected" onClick={handleDelete} - disabled={!selectedId} + disabled={!selectedTitle} > @@ -179,44 +231,129 @@ export const RecipePage: React.FC = () => {
RECIPE EDITOR - {editedRecipe && {editedRecipe.id}} + {editedRecipe && {editedRecipe.Title || selectedTitle}}
{editedRecipe ? ( -
-
- - setEditedRecipe({ ...editedRecipe, name: e.target.value })} - className="w-full bg-black/40 border border-white/10 rounded px-4 py-3 text-white focus:border-neon-blue focus:outline-none transition-colors font-mono" - /> -
+
+ {/* Basic Settings */} +
+

Basic Settings

-
- - -
- -
-
- - +
+
+ + handleFieldChange('Motion', e.target.value)} + className="w-full bg-black/40 border border-white/10 rounded px-4 py-2 text-white focus:border-neon-blue focus:outline-none transition-colors font-mono text-sm" + /> +
+
+ + handleFieldChange('AutoOutConveyor', parseInt(e.target.value) || 0)} + className="w-full bg-black/40 border border-white/10 rounded px-4 py-2 text-white focus:border-neon-blue focus:outline-none transition-colors font-mono text-sm" + /> +
-
- - + +
+
+ + handleFieldChange('Def_VName', e.target.value)} + className="w-full bg-black/40 border border-white/10 rounded px-4 py-2 text-white focus:border-neon-blue focus:outline-none transition-colors font-mono text-sm" + /> +
+
+ + handleFieldChange('Def_MFG', e.target.value)} + className="w-full bg-black/40 border border-white/10 rounded px-4 py-2 text-white focus:border-neon-blue focus:outline-none transition-colors font-mono text-sm" + /> +
- {/* Placeholder for more parameters */} -
- Additional process parameters would go here... + {/* Barcode Settings */} +
+

Barcode Settings

+
+ + + +
+
+ + {/* Feature Flags */} +
+

Feature Flags

+
+ {[ + { key: 'IgnoreOtherBarcode', label: 'Ignore Other Barcode' }, + { key: 'DisableCamera', label: 'Disable Camera' }, + { key: 'DisablePrinter', label: 'Disable Printer' }, + { key: 'CheckSIDExsit', label: 'Check SID Exist' }, + { key: 'bOwnZPL', label: 'Own ZPL' }, + { key: 'IgnorePartNo', label: 'Ignore Part No' }, + { key: 'IgnoreBatch', label: 'Ignore Batch' }, + ].map(({ key, label }) => ( + + ))} +
) : ( @@ -228,15 +365,20 @@ export const RecipePage: React.FC = () => {
{/* Editor Actions */} -
- - SAVE CHANGES - +
+ {hasChanges && ( + ● UNSAVED CHANGES + )} +
+ + SAVE CHANGES + +
diff --git a/FrontEnd/types.ts b/FrontEnd/types.ts index c241bdc..f82257a 100644 --- a/FrontEnd/types.ts +++ b/FrontEnd/types.ts @@ -65,6 +65,8 @@ declare global { GetConfig(): Promise; GetIOList(): Promise; GetRecipeList(): Promise; + GetRecipe(recipeTitle: string): Promise; + SaveRecipe(recipeTitle: string, recipeData: string): Promise; SaveConfig(configJson: string): Promise; } };