2026-05-07 08:55:18 +02:00
import React , { useState , useRef , useCallback , useEffect } from 'react' ;
2026-04-22 16:52:59 +02:00
import { ExcelRow } from '../types' ;
import { useColumns } from '../contexts/ColumnsContext' ;
2026-04-11 11:34:57 +02:00
import { X , Sparkles , Save , Loader2 , Languages , Package , CheckCircle2 , Mic , MicOff } from 'lucide-react' ;
2026-03-27 13:58:20 +01:00
import { generateGemini } from '../services/gemini' ;
2026-03-27 11:34:09 +01:00
import { cn } from '../lib/utils' ;
2026-04-11 11:34:57 +02:00
import { useSpeechRecognition } from '../lib/useSpeechRecognition' ;
2026-03-29 18:26:20 +02:00
import { ConfirmModal } from './ConfirmModal' ;
2026-03-27 11:34:09 +01:00
interface EditPanelProps {
row : ExcelRow ;
rowIndex : number ;
2026-04-09 08:45:22 +02:00
onSave : ( rowIndex : number , updatedRow : ExcelRow ) => void ;
2026-03-27 11:34:09 +01:00
onClose : () => void ;
2026-03-29 17:35:07 +02:00
onCaptureState : ( message : string ) => void ;
2026-03-27 11:34:09 +01:00
}
2026-04-09 10:57:39 +02:00
interface DimensionInputProps {
label : string ;
field : string ;
value : string ;
isModified : boolean ;
onChange : ( val : string ) => void ;
placeholder? : string ;
}
const DimensionInput = ({ label , value , isModified , onChange , placeholder } : DimensionInputProps ) => (
< div className = "flex flex-col gap-1.5" >
< label className = "text-[10px] font-medium text-slate-500 uppercase tracking-wider" >{ label }</ label >
< input
type = "text"
value = { value }
onChange = { e => onChange ( e . target . value )}
placeholder = { placeholder }
className = { cn (
"w-full bg-slate-900 border rounded p-2 text-sm text-white focus:outline-none focus:ring-1 transition-colors" ,
isModified ? "border-blue-500 focus:ring-blue-500" : "border-slate-700 focus:border-slate-500"
)}
/>
</ div >
);
2026-05-07 08:55:18 +02:00
const AutoResizeTextarea = ({
id ,
value ,
onChange ,
onKeyDown ,
className ,
placeholder
} : {
id? : string ;
value : string ;
onChange : ( e : React.ChangeEvent < HTMLTextAreaElement >) => void ;
onKeyDown ?: ( e : React.KeyboardEvent ) => void ;
className? : string ;
placeholder? : string ;
}) => {
const textareaRef = useRef < HTMLTextAreaElement >( null );
const adjustHeight = useCallback (() => {
const textarea = textareaRef . current ;
if ( textarea ) {
textarea . style . height = 'auto' ;
textarea . style . height = ` ${ textarea . scrollHeight } px` ;
}
}, []);
useEffect (() => {
adjustHeight ();
}, [ value , adjustHeight ]);
return (
< textarea
id = { id }
ref = { textareaRef }
value = { value }
onChange = { onChange }
onKeyDown = { onKeyDown }
className = { cn ( className , "overflow-hidden resize-none" )}
placeholder = { placeholder }
/>
);
};
2026-04-09 10:57:39 +02:00
interface FieldEditorProps {
title : string ;
field : string ;
value : string ;
isModified : boolean ;
canTranslate : boolean ;
isGenerated : boolean ;
isLoading : boolean ;
onGenerate : () => void ;
onChange : ( val : string ) => void ;
onKeyDown : ( e : React.KeyboardEvent ) => void ;
2026-04-11 11:34:57 +02:00
isListening? : boolean ;
onToggleVoice ?: () => void ;
voiceSupported? : boolean ;
2026-04-09 10:57:39 +02:00
}
const FieldEditor = ({
title ,
field ,
value ,
isModified ,
canTranslate ,
isGenerated ,
isLoading ,
onGenerate ,
onChange ,
2026-04-11 11:34:57 +02:00
onKeyDown ,
isListening ,
onToggleVoice ,
voiceSupported
2026-04-09 10:57:39 +02:00
} : FieldEditorProps ) => (
< div className = "flex flex-col gap-2" >
< div className = "flex items-center justify-between" >
< label className = "text-sm font-medium text-slate-300" >{ title }</ label >
< div className = "flex items-center gap-2" >
{ canTranslate && (
< div className = "flex items-center gap-1 text-[10px] text-slate-500 italic" >
< Languages className = "w-3 h-3" />
Can translate from existing
</ div >
)}
2026-04-11 11:34:57 +02:00
{ voiceSupported && (
< button
onClick = { onToggleVoice }
className = { cn (
"flex items-center gap-1.5 text-xs font-medium px-2 py-1 rounded transition-colors" ,
isListening
? "bg-red-600/20 text-red-400 animate-pulse"
: "bg-slate-700/50 text-slate-400 hover:bg-slate-600 hover:text-white"
)}
title = { isListening ? "Stop recording" : "Voice input" }
>
{ isListening ? < MicOff className = "w-3 h-3" /> : < Mic className = "w-3 h-3" />}
{ isListening ? 'Stop' : 'Voice' }
</ button >
)}
2026-04-09 10:57:39 +02:00
< button
onClick = { onGenerate }
disabled = { isLoading }
className = "flex items-center gap-1.5 text-xs font-medium bg-blue-600/20 text-blue-400 hover:bg-blue-600 hover:text-white px-2 py-1 rounded transition-colors disabled:opacity-50"
>
{ isLoading ? < Loader2 className = "w-3 h-3 animate-spin" /> : < Sparkles className = "w-3 h-3" />}
{ canTranslate ? 'Translate with Gemini' : 'Generate with Gemini' }
</ button >
</ div >
{ isGenerated && (
< div className = "flex items-center gap-1.5 text-[10px] text-emerald-400 bg-emerald-400/10 px-2 py-0.5 rounded w-fit animate-in fade-in slide-in-from-top-1 duration-300" >
< CheckCircle2 className = "w-3 h-3" />
AI Generated - You can still edit manually
</ div >
)}
</ div >
2026-05-07 08:55:18 +02:00
< AutoResizeTextarea
2026-04-09 10:57:39 +02:00
id = { `field- ${ field } ` }
value = { value }
onChange = { e => onChange ( e . target . value )}
onKeyDown = { onKeyDown }
className = { cn (
2026-05-07 08:55:18 +02:00
"w-full min-h-[128px] bg-slate-900 border rounded-md p-3 text-sm text-white focus:outline-none focus:ring-1 transition-colors" ,
2026-04-09 10:57:39 +02:00
isModified ? "border-blue-500 focus:ring-blue-500" : "border-slate-700 focus:border-slate-500 focus:ring-slate-500"
)}
placeholder = { `Enter ${ title } ...` }
/>
</ div >
);
2026-03-29 17:35:07 +02:00
export function EditPanel ({ row , rowIndex , onSave , onClose , onCaptureState } : EditPanelProps ) {
2026-04-22 16:52:59 +02:00
const COLUMNS = useColumns ();
2026-03-27 11:34:09 +01:00
const [ formData , setFormData ] = useState ({
longDe : row [ COLUMNS . LONG_DE ] || '' ,
longEn : row [ COLUMNS . LONG_EN ] || '' ,
shortDe : row [ COLUMNS . SHORT_DE ] || '' ,
shortEn : row [ COLUMNS . SHORT_EN ] || '' ,
2026-04-07 13:38:21 +02:00
detailsDe : row [ COLUMNS . DETAILS_DE ] || '' ,
detailsEn : row [ COLUMNS . DETAILS_EN ] || '' ,
2026-04-10 12:55:47 +02:00
innerW : row [ COLUMNS . INNER_W ] !== undefined && row [ COLUMNS . INNER_W ] !== null ? String ( row [ COLUMNS . INNER_W ]) : '' ,
innerL : row [ COLUMNS . INNER_L ] !== undefined && row [ COLUMNS . INNER_L ] !== null ? String ( row [ COLUMNS . INNER_L ]) : '' ,
innerH : row [ COLUMNS . INNER_H ] !== undefined && row [ COLUMNS . INNER_H ] !== null ? String ( row [ COLUMNS . INNER_H ]) : '' ,
outerW : row [ COLUMNS . OUTER_W ] !== undefined && row [ COLUMNS . OUTER_W ] !== null ? String ( row [ COLUMNS . OUTER_W ]) : '' ,
outerL : row [ COLUMNS . OUTER_L ] !== undefined && row [ COLUMNS . OUTER_L ] !== null ? String ( row [ COLUMNS . OUTER_L ]) : '' ,
outerH : row [ COLUMNS . OUTER_H ] !== undefined && row [ COLUMNS . OUTER_H ] !== null ? String ( row [ COLUMNS . OUTER_H ]) : '' ,
unitsOuter : row [ COLUMNS . UNITS_OUTER ] !== undefined && row [ COLUMNS . UNITS_OUTER ] !== null ? String ( row [ COLUMNS . UNITS_OUTER ]) : '' ,
moq : row [ COLUMNS . MOQ ] !== undefined && row [ COLUMNS . MOQ ] !== null ? String ( row [ COLUMNS . MOQ ]) : '' ,
2026-04-23 12:58:51 +02:00
productType : row [ COLUMNS . PRODUCT_TYPE ] !== undefined && row [ COLUMNS . PRODUCT_TYPE ] !== null ? String ( row [ COLUMNS . PRODUCT_TYPE ]) : '' ,
itemToLogistic : row [ COLUMNS . ITEM_TO_LOGISTIC ] !== undefined && row [ COLUMNS . ITEM_TO_LOGISTIC ] !== null ? String ( row [ COLUMNS . ITEM_TO_LOGISTIC ]) : '' ,
2026-03-27 11:34:09 +01:00
});
const [ loadingField , setLoadingField ] = useState < string | null >( null );
const [ error , setError ] = useState < string | null >( null );
2026-03-29 18:26:20 +02:00
const [ isConfirmOpen , setIsConfirmOpen ] = useState ( false );
2026-03-29 18:29:11 +02:00
const [ pendingGeminiField , setPendingGeminiField ] = useState < keyof typeof formData | null >( null );
2026-04-08 16:10:14 +02:00
const [ generatedFields , setGeneratedFields ] = useState < Set < string >>( new Set ());
2026-03-27 11:34:09 +01:00
2026-04-11 11:34:57 +02:00
const handleSpeechResult = useCallback (( transcript : string ) => {
setFormData ( prev => ({ ... prev , longDe : prev.longDe + ' ' + transcript }));
}, []);
const { isListening : isListeningDe , start : startListeningDe , stop : stopListeningDe , isSupported : speechSupported } = useSpeechRecognition ({ onResult : handleSpeechResult });
const handleSpeechResultEn = useCallback (( transcript : string ) => {
setFormData ( prev => ({ ... prev , longEn : prev.longEn + ' ' + transcript }));
}, []);
const { isListening : isListeningEn , start : startListeningEn , stop : stopListeningEn } = useSpeechRecognition ({ onResult : handleSpeechResultEn , lang : 'en-US' });
2026-03-27 11:34:09 +01:00
const isModified = ( field : keyof typeof formData ) => {
2026-03-29 17:30:04 +02:00
const colMap : Record < string , number > = {
2026-03-27 11:34:09 +01:00
longDe : COLUMNS.LONG_DE ,
longEn : COLUMNS.LONG_EN ,
shortDe : COLUMNS.SHORT_DE ,
shortEn : COLUMNS.SHORT_EN ,
2026-03-29 17:30:04 +02:00
innerW : COLUMNS.INNER_W ,
innerL : COLUMNS.INNER_L ,
innerH : COLUMNS.INNER_H ,
outerW : COLUMNS.OUTER_W ,
outerL : COLUMNS.OUTER_L ,
outerH : COLUMNS.OUTER_H ,
unitsOuter : COLUMNS.UNITS_OUTER ,
moq : COLUMNS.MOQ ,
2026-04-07 13:38:21 +02:00
detailsDe : COLUMNS.DETAILS_DE ,
detailsEn : COLUMNS.DETAILS_EN ,
2026-04-23 12:58:51 +02:00
productType : COLUMNS.PRODUCT_TYPE ,
itemToLogistic : COLUMNS.ITEM_TO_LOGISTIC ,
2026-03-27 11:34:09 +01:00
};
2026-04-07 13:38:21 +02:00
const colIndex = ( colMap as Record < string , number >)[ field as string ];
if ( colIndex === undefined ) return false ;
2026-04-10 12:55:47 +02:00
return formData [ field ] !== ( row [ colIndex ] !== undefined && row [ colIndex ] !== null ? String ( row [ colIndex ]) : '' );
2026-03-27 11:34:09 +01:00
};
2026-04-24 13:27:34 +02:00
const handleGenerate = ( field : keyof typeof formData ) => {
console . log ( '[EditPanel] handleGenerate called for field:' , field );
setError ( null ); // Clear any previous error
2026-03-29 18:29:11 +02:00
setPendingGeminiField ( field );
};
const executeGenerate = async () => {
2026-04-24 13:27:34 +02:00
if ( ! pendingGeminiField ) {
console . warn ( '[EditPanel] executeGenerate called but no field pending' );
return ;
}
2026-03-29 18:29:11 +02:00
const field = pendingGeminiField ;
2026-04-24 13:27:34 +02:00
console . log ( '[EditPanel] Starting generation for field:' , field );
2026-03-27 11:34:09 +01:00
setLoadingField ( field );
setError ( null );
2026-04-24 13:27:34 +02:00
2026-03-27 11:34:09 +01:00
try {
let prompt = '' ;
2026-04-07 13:38:21 +02:00
const baseContext = `Article Name: ${ row [ COLUMNS . ARTICLE_NAME ] } \ nArticle Details (EN): ${ formData . detailsEn || 'N/A' } \ nArticle Details (DE): ${ formData . detailsDe || 'N/A' } ` ;
2026-03-27 11:34:09 +01:00
2026-04-14 11:23:49 +02:00
const systemPrompt = "You are a professional copywriter and expert translator for CRAZE GmbH, a German toy company. CRITICAL: Output ONLY the translated or generated content. Do not include any introductions, conclusions, or conversational text. Translate EVERYTHING, including phrases in ALL CAPS (maintain the all-caps casing for those phrases in the translation). Your response must be complete, with all sentences finished and logically concluded. Never end mid-sentence." ;
2026-03-27 11:34:09 +01:00
if ( field === 'longDe' ) {
2026-04-07 08:35:04 +02:00
if ( formData . longEn ) {
prompt = `Translate the EVERYTHING below from English into professional German.
CRITICAL INSTRUCTIONS:
1. Do NOT summarize.
2. Maintain the EXACT same length and detailed information.
3. Keep all technical specs intact.
4. Translate every single paragraph into natural, commercial German for toy buyers.
2026-04-09 11:05:00 +02:00
5. Translate ALL text, including titles or phrases in ALL CAPS, and keep them in ALL CAPS in the translation.
2026-04-14 11:23:49 +02:00
6. ENSURE the translation is complete and does not cut off. The final sentence must be fully finished.
2026-04-07 08:35:04 +02:00
English Text to Translate:
${ formData . longEn } ` ;
} else {
2026-04-14 11:23:49 +02:00
prompt = `Based on the following product details, generate a long commercial description in German. It should be fluent, oriented towards B2B toy buyers, 3-5 paragraphs long. Include features, benefits, and material if available.
IMPORTANT: Ensure the description is complete and ends with a finished sentence. \ n \ n ${ baseContext } ` ;
2026-04-07 08:35:04 +02:00
}
2026-03-27 11:34:09 +01:00
} else if ( field === 'longEn' ) {
if ( formData . longDe ) {
2026-03-27 14:15:51 +01:00
prompt = `Translate the EVERYTHING below from German into professional English.
CRITICAL INSTRUCTIONS:
1. Do NOT summarize.
2. Maintain the EXACT same length and detailed information.
3. Keep all technical specs intact.
2026-04-07 08:35:04 +02:00
4. Translate every single paragraph into natural, commercial English for toy buyers.
2026-04-09 11:05:00 +02:00
5. Translate ALL text, including titles or phrases in ALL CAPS, and keep them in ALL CAPS in the translation.
2026-04-14 11:23:49 +02:00
6. ENSURE the translation is complete and does not cut off. The final sentence must be fully finished.
2026-03-27 14:15:51 +01:00
German Text to Translate:
${ formData . longDe } ` ;
2026-03-27 11:34:09 +01:00
} else {
2026-04-14 11:23:49 +02:00
prompt = `Based on the following product details, generate a long commercial description in English. It should be fluent, oriented towards B2B toy buyers, 3-5 paragraphs long.
IMPORTANT: Ensure the description is complete and ends with a finished sentence. \ n \ n ${ baseContext } ` ;
2026-03-27 11:34:09 +01:00
}
} else if ( field === 'shortDe' ) {
2026-04-07 08:35:04 +02:00
if ( formData . shortEn ) {
2026-04-14 11:23:49 +02:00
prompt = `Translate exactly this short English product description into professional German for the toy market.
IMPORTANT:
1. Translate everything, including any ALL CAPS phrases, maintaining the all-caps formatting.
2. ENSURE the translation is complete and does not cut off mid-sentence. \ n \ n ${ formData . shortEn } ` ;
2026-04-07 08:35:04 +02:00
} else if ( formData . longDe ) {
2026-04-14 11:23:49 +02:00
prompt = `Create a concise summary of the following German product description.
2026-04-20 16:50:21 +02:00
CRITICAL: The summary MUST be between 250 and 450 characters long.
2026-04-14 11:23:49 +02:00
Preserve the most important commercial highlights and features in natural, professional German for B2B toy buyers.
Do not use bullet points — write flowing prose.
IMPORTANT: Ensure the summary is a complete thought and ends with a finished sentence. \ n \ nOriginal description: \ n ${ formData . longDe } ` ;
2026-03-27 11:34:09 +01:00
} else {
2026-04-20 16:50:21 +02:00
prompt = `Based on the following product details, generate a short commercial description in German.
CRITICAL: Length MUST be between 250 and 450 characters.
Ensure the text is complete and ends with a finished sentence. \ n \ n ${ baseContext } ` ;
2026-03-27 11:34:09 +01:00
}
} else if ( field === 'shortEn' ) {
if ( formData . shortDe ) {
2026-04-14 11:23:49 +02:00
prompt = `Translate exactly this short German product description into professional English for the toy market.
IMPORTANT:
1. Translate everything, including any ALL CAPS phrases, maintaining the all-caps formatting.
2. ENSURE the translation is complete and does not cut off mid-sentence. \ n \ n ${ formData . shortDe } ` ;
2026-03-27 13:58:20 +01:00
} else if ( formData . longEn ) {
2026-04-14 11:23:49 +02:00
prompt = `Create a concise summary of the following English product description.
2026-04-20 16:50:21 +02:00
CRITICAL: The summary MUST be between 250 and 450 characters long.
2026-04-14 11:23:49 +02:00
Preserve the most important commercial highlights and features in natural, professional English for B2B toy buyers.
Do not use bullet points — write flowing prose.
IMPORTANT: Ensure the summary is a complete thought and ends with a finished sentence. \ n \ nOriginal description: \ n ${ formData . longEn } ` ;
2026-03-27 11:34:09 +01:00
} else {
2026-04-20 16:50:21 +02:00
prompt = `Based on the following product details, generate a short commercial description in English.
CRITICAL: Length MUST be between 250 and 450 characters.
Ensure the text is complete and ends with a finished sentence. \ n \ n ${ baseContext } ` ;
2026-03-27 11:34:09 +01:00
}
}
2026-03-27 13:58:20 +01:00
const generatedText = await generateGemini ( prompt , systemPrompt );
2026-03-27 11:34:09 +01:00
setFormData ( prev => ({ ... prev , [ field ] : generatedText . trim () }));
2026-04-08 16:10:14 +02:00
setGeneratedFields ( prev => new Set ( prev ). add ( field ));
2026-04-09 10:20:59 +02:00
// Focus the textarea so user can edit immediately after AI generation
setTimeout (() => {
const textarea = document . getElementById ( `field- ${ field } ` );
if ( textarea ) textarea . focus ();
}, 100 );
2026-04-24 13:23:18 +02:00
setPendingGeminiField ( null );
2026-03-27 11:34:09 +01:00
} catch ( err : any ) {
setError ( err . message || 'An error occurred during generation.' );
2026-04-24 13:23:18 +02:00
setPendingGeminiField ( null ); // Close modal on error so error message is visible in panel
2026-03-27 11:34:09 +01:00
} finally {
setLoadingField ( null );
}
};
2026-04-09 08:45:22 +02:00
const handleSave = () => {
2026-03-29 18:26:20 +02:00
setIsConfirmOpen ( false );
2026-03-29 17:35:07 +02:00
const hasModifications = Object . keys ( formData ). some ( k => isModified ( k as keyof typeof formData ));
if ( hasModifications ) {
onCaptureState ( `Updated product ${ row [ COLUMNS . ARTICLE_NO ] } ` );
}
2026-03-27 11:34:09 +01:00
const newRow = [... row ];
newRow [ COLUMNS . LONG_DE ] = formData . longDe ;
newRow [ COLUMNS . LONG_EN ] = formData . longEn ;
newRow [ COLUMNS . SHORT_DE ] = formData . shortDe ;
newRow [ COLUMNS . SHORT_EN ] = formData . shortEn ;
2026-03-29 17:30:04 +02:00
newRow [ COLUMNS . INNER_W ] = formData . innerW ;
newRow [ COLUMNS . INNER_L ] = formData . innerL ;
newRow [ COLUMNS . INNER_H ] = formData . innerH ;
newRow [ COLUMNS . OUTER_W ] = formData . outerW ;
newRow [ COLUMNS . OUTER_L ] = formData . outerL ;
newRow [ COLUMNS . OUTER_H ] = formData . outerH ;
newRow [ COLUMNS . UNITS_OUTER ] = formData . unitsOuter ;
newRow [ COLUMNS . MOQ ] = formData . moq ;
2026-04-07 13:38:21 +02:00
newRow [ COLUMNS . DETAILS_DE ] = formData . detailsDe ;
newRow [ COLUMNS . DETAILS_EN ] = formData . detailsEn ;
2026-04-23 12:58:51 +02:00
newRow [ COLUMNS . PRODUCT_TYPE ] = formData . productType ;
newRow [ COLUMNS . ITEM_TO_LOGISTIC ] = formData . itemToLogistic ;
2026-04-09 08:45:22 +02:00
onSave ( rowIndex , newRow );
2026-03-27 11:34:09 +01:00
};
2026-04-09 10:41:05 +02:00
const handleInputKeyDown = ( e : React.KeyboardEvent ) => {
e . stopPropagation ();
};
2026-03-27 11:34:09 +01:00
return (
<>
< div className = "fixed inset-0 bg-slate-950/50 backdrop-blur-sm z-40" onClick = { onClose } />
< div className = "fixed right-0 top-0 bottom-0 w-[600px] bg-slate-800 border-l border-slate-700 shadow-2xl z-50 flex flex-col animate-in slide-in-from-right duration-200" >
< div className = "flex items-center justify-between p-6 border-b border-slate-700 bg-slate-800/50" >
< div >
< h2 className = "text-xl font-bold text-white" > Edit Product </ h2 >
< p className = "text-sm text-slate-400 mt-1" >{ row [ COLUMNS . ARTICLE_NO ]} - { row [ COLUMNS . ARTICLE_NAME ]}</ p >
</ div >
< button onClick = { onClose } className = "p-2 text-slate-400 hover:text-white hover:bg-slate-700 rounded-full transition-colors" >
< X className = "w-5 h-5" />
</ button >
</ div >
< div className = "flex-1 overflow-auto p-6 space-y-6" >
{ error && (
< div className = "bg-red-500/10 border border-red-500/50 text-red-400 p-4 rounded-md text-sm" >
{ error }
</ div >
)}
< div className = "grid grid-cols-2 gap-4 bg-slate-900/50 p-4 rounded-lg border border-slate-700/50" >
< div >
< span className = "block text-xs text-slate-500 mb-1" > Line </ span >
< span className = "text-sm text-slate-300 font-medium" >{ row [ COLUMNS . LINE ] || '-' }</ span >
</ div >
< div >
< span className = "block text-xs text-slate-500 mb-1" > License </ span >
< span className = "text-sm text-slate-300 font-medium" >{ row [ COLUMNS . LICENSE ] || '-' }</ span >
</ div >
< div className = "col-span-2" >
< span className = "block text-xs text-slate-500 mb-1" > Details ( DE )</ span >
2026-05-07 08:55:18 +02:00
< AutoResizeTextarea
2026-04-07 13:38:21 +02:00
value = { formData . detailsDe }
onChange = { e => setFormData ( prev => ({ ... prev , detailsDe : e.target.value }))}
className = { cn (
2026-05-07 08:55:18 +02:00
"w-full min-h-[80px] bg-slate-900 border rounded p-2 text-sm text-white focus:outline-none focus:ring-1 transition-colors" ,
2026-04-07 13:38:21 +02:00
isModified ( 'detailsDe' ) ? "border-blue-500 focus:ring-blue-500" : "border-slate-700/50"
)}
placeholder = "Enter details in German..."
/>
2026-03-27 11:34:09 +01:00
</ div >
< div className = "col-span-2" >
< span className = "block text-xs text-slate-500 mb-1" > Details ( EN )</ span >
2026-05-07 08:55:18 +02:00
< AutoResizeTextarea
2026-04-07 13:38:21 +02:00
value = { formData . detailsEn }
onChange = { e => setFormData ( prev => ({ ... prev , detailsEn : e.target.value }))}
className = { cn (
2026-05-07 08:55:18 +02:00
"w-full min-h-[80px] bg-slate-900 border rounded p-2 text-sm text-white focus:outline-none focus:ring-1 transition-colors" ,
2026-04-07 13:38:21 +02:00
isModified ( 'detailsEn' ) ? "border-blue-500 focus:ring-blue-500" : "border-slate-700/50"
)}
placeholder = "Enter details in English..."
/>
2026-03-27 11:34:09 +01:00
</ div >
2026-03-29 17:30:04 +02:00
</ div >
< div className = "space-y-4 bg-slate-900/30 p-4 rounded-lg border border-slate-700/50" >
< h3 className = "text-xs font-semibold text-slate-400 uppercase tracking-widest flex items-center gap-2" >
< Package className = "w-3 h-3" /> Dimensions & Packaging
</ h3 >
< div className = "grid grid-cols-3 gap-3" >
< div className = "col-span-3 text-[10px] text-slate-500 font-medium" > INNER BOX ( L × W × H ) cm </ div >
2026-04-09 10:57:39 +02:00
< DimensionInput label = "Length" value = { formData . innerL } field = "innerL" isModified = { isModified ( 'innerL' )} onChange = {( val ) => setFormData ( p => ({... p , innerL : val }))} placeholder = "L" />
< DimensionInput label = "Width" value = { formData . innerW } field = "innerW" isModified = { isModified ( 'innerW' )} onChange = {( val ) => setFormData ( p => ({... p , innerW : val }))} placeholder = "W" />
< DimensionInput label = "Height" value = { formData . innerH } field = "innerH" isModified = { isModified ( 'innerH' )} onChange = {( val ) => setFormData ( p => ({... p , innerH : val }))} placeholder = "H" />
2026-03-29 17:30:04 +02:00
</ div >
< div className = "grid grid-cols-3 gap-3" >
< div className = "col-span-3 text-[10px] text-slate-500 font-medium" > OUTER BOX ( L × W × H ) cm </ div >
2026-04-09 10:57:39 +02:00
< DimensionInput label = "Length" value = { formData . outerL } field = "outerL" isModified = { isModified ( 'outerL' )} onChange = {( val ) => setFormData ( p => ({... p , outerL : val }))} placeholder = "L" />
< DimensionInput label = "Width" value = { formData . outerW } field = "outerW" isModified = { isModified ( 'outerW' )} onChange = {( val ) => setFormData ( p => ({... p , outerW : val }))} placeholder = "W" />
< DimensionInput label = "Height" value = { formData . outerH } field = "outerH" isModified = { isModified ( 'outerH' )} onChange = {( val ) => setFormData ( p => ({... p , outerH : val }))} placeholder = "H" />
2026-03-29 17:30:04 +02:00
</ div >
< div className = "grid grid-cols-2 gap-3" >
2026-04-09 10:57:39 +02:00
< DimensionInput label = "Units per Outer" value = { formData . unitsOuter } field = "unitsOuter" isModified = { isModified ( 'unitsOuter' )} onChange = {( val ) => setFormData ( p => ({... p , unitsOuter : val }))} />
< DimensionInput label = "MOQ" value = { formData . moq } field = "moq" isModified = { isModified ( 'moq' )} onChange = {( val ) => setFormData ( p => ({... p , moq : val }))} />
2026-03-29 17:30:04 +02:00
</ div >
2026-04-23 12:58:51 +02:00
< div className = "grid grid-cols-2 gap-3 pt-2 border-t border-slate-700/30" >
< DimensionInput label = "Type" value = { formData . productType } field = "productType" isModified = { isModified ( 'productType' )} onChange = {( val ) => setFormData ( p => ({... p , productType : val }))} />
< DimensionInput label = "Item to Logistic" value = { formData . itemToLogistic } field = "itemToLogistic" isModified = { isModified ( 'itemToLogistic' )} onChange = {( val ) => setFormData ( p => ({... p , itemToLogistic : val }))} />
</ div >
2026-03-27 11:34:09 +01:00
</ div >
2026-04-09 10:57:39 +02:00
< FieldEditor
title = "Long Description (DE)"
field = "longDe"
value = { formData . longDe }
isModified = { isModified ( 'longDe' )}
canTranslate = { !! formData . longEn }
isGenerated = { generatedFields . has ( 'longDe' )}
isLoading = { loadingField === 'longDe' }
onGenerate = {() => handleGenerate ( 'longDe' )}
onChange = {( val ) => setFormData ( p => ({... p , longDe : val }))}
onKeyDown = { handleInputKeyDown }
2026-04-11 11:34:57 +02:00
isListening = { isListeningDe }
onToggleVoice = { isListeningDe ? stopListeningDe : startListeningDe }
voiceSupported = { speechSupported }
2026-04-09 10:57:39 +02:00
/>
< FieldEditor
title = "Long Description (EN)"
field = "longEn"
value = { formData . longEn }
isModified = { isModified ( 'longEn' )}
canTranslate = { !! formData . longDe }
isGenerated = { generatedFields . has ( 'longEn' )}
isLoading = { loadingField === 'longEn' }
onGenerate = {() => handleGenerate ( 'longEn' )}
onChange = {( val ) => setFormData ( p => ({... p , longEn : val }))}
onKeyDown = { handleInputKeyDown }
2026-04-11 11:34:57 +02:00
isListening = { isListeningEn }
onToggleVoice = { isListeningEn ? stopListeningEn : startListeningEn }
voiceSupported = { speechSupported }
2026-04-09 10:57:39 +02:00
/>
< FieldEditor
title = "Short Description (DE)"
field = "shortDe"
value = { formData . shortDe }
isModified = { isModified ( 'shortDe' )}
2026-04-20 16:53:21 +02:00
canTranslate = { !! formData . shortEn }
2026-04-09 10:57:39 +02:00
isGenerated = { generatedFields . has ( 'shortDe' )}
isLoading = { loadingField === 'shortDe' }
onGenerate = {() => handleGenerate ( 'shortDe' )}
onChange = {( val ) => setFormData ( p => ({... p , shortDe : val }))}
onKeyDown = { handleInputKeyDown }
/>
< FieldEditor
title = "Short Description (EN)"
field = "shortEn"
value = { formData . shortEn }
isModified = { isModified ( 'shortEn' )}
2026-04-20 16:53:21 +02:00
canTranslate = { !! formData . shortDe }
2026-04-09 10:57:39 +02:00
isGenerated = { generatedFields . has ( 'shortEn' )}
isLoading = { loadingField === 'shortEn' }
onGenerate = {() => handleGenerate ( 'shortEn' )}
onChange = {( val ) => setFormData ( p => ({... p , shortEn : val }))}
onKeyDown = { handleInputKeyDown }
/>
2026-03-27 11:34:09 +01:00
</ div >
< div className = "p-6 border-t border-slate-700 bg-slate-800/50 flex justify-end gap-3" >
< button
onClick = { onClose }
2026-04-09 08:45:22 +02:00
className = "px-4 py-2 text-sm font-medium text-slate-300 hover:text-white hover:bg-slate-700 rounded-md transition-colors"
2026-03-27 11:34:09 +01:00
>
Cancel
</ button >
< button
2026-03-29 18:26:20 +02:00
onClick = {() => setIsConfirmOpen ( true )}
2026-04-09 08:45:22 +02:00
className = "flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-md shadow-lg shadow-blue-900/20 transition-colors"
2026-03-27 11:34:09 +01:00
>
2026-04-09 08:45:22 +02:00
< Save className = "w-4 h-4" />
Queue Changes
2026-03-27 11:34:09 +01:00
</ button >
</ div >
2026-03-29 18:26:20 +02:00
< ConfirmModal
isOpen = { isConfirmOpen }
onConfirm = { handleSave }
onCancel = {() => setIsConfirmOpen ( false )}
title = "Confirm Changes"
message = { `Are you sure you want to save the modifications for product ${ row [ COLUMNS . ARTICLE_NO ] } ?` }
type = "info"
confirmText = "Save changes"
/>
2026-03-29 18:29:11 +02:00
< ConfirmModal
isOpen = { !! pendingGeminiField }
2026-04-24 13:23:18 +02:00
isLoading = { loadingField !== null }
2026-03-29 18:29:11 +02:00
onConfirm = { executeGenerate }
onCancel = {() => setPendingGeminiField ( null )}
title = "Confirm AI Action"
2026-04-20 16:53:21 +02:00
message = { `Are you sure you want to use Gemini to ${
(( pendingGeminiField === 'shortEn' && formData . shortDe ) ||
( pendingGeminiField === 'shortDe' && formData . shortEn ) ||
( pendingGeminiField === 'longEn' && formData . longDe ) ||
( pendingGeminiField === 'longDe' && formData . longEn ))
? 'translate' : 'generate' } the ${ pendingGeminiField } field?` }
2026-03-29 18:29:11 +02:00
type = "info"
confirmText = "Start Generation"
/>
2026-03-27 11:34:09 +01:00
</ div >
</>
);
}