2026-03-27 11:34:09 +01:00
import React , { useState } from 'react' ;
import { ExcelRow , COLUMNS } from '../types' ;
2026-03-29 17:30:04 +02:00
import { X , Sparkles , Save , Loader2 , Languages , Package } 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-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 ;
onSave : ( rowIndex : number , updatedRow : ExcelRow ) => void ;
onClose : () => void ;
2026-03-29 17:35:07 +02:00
onCaptureState : ( message : string ) => void ;
2026-03-27 11:34:09 +01:00
}
2026-03-29 17:35:07 +02:00
export function EditPanel ({ row , rowIndex , onSave , onClose , onCaptureState } : EditPanelProps ) {
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-03-29 17:30:04 +02:00
innerW : row [ COLUMNS . INNER_W ] || '' ,
innerL : row [ COLUMNS . INNER_L ] || '' ,
innerH : row [ COLUMNS . INNER_H ] || '' ,
outerW : row [ COLUMNS . OUTER_W ] || '' ,
outerL : row [ COLUMNS . OUTER_L ] || '' ,
outerH : row [ COLUMNS . OUTER_H ] || '' ,
unitsOuter : row [ COLUMNS . UNITS_OUTER ] || '' ,
moq : row [ COLUMNS . MOQ ] || '' ,
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-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-03-27 11:34:09 +01:00
};
return formData [ field ] !== ( row [ colMap [ field ]] || '' );
};
const handleGenerate = async ( field : keyof typeof formData ) => {
2026-03-29 18:29:11 +02:00
setPendingGeminiField ( field );
};
const executeGenerate = async () => {
if ( ! pendingGeminiField ) return ;
const field = pendingGeminiField ;
setPendingGeminiField ( null );
2026-03-27 11:34:09 +01:00
setLoadingField ( field );
setError ( null );
try {
let prompt = '' ;
const baseContext = `Article Name: ${ row [ COLUMNS . ARTICLE_NAME ] } \ nArticle Details (EN): ${ row [ COLUMNS . DETAILS_EN ] || 'N/A' } \ nArticle Details (DE): ${ row [ COLUMNS . DETAILS_DE ] || 'N/A' } ` ;
2026-03-27 13:58:20 +01:00
const systemPrompt = "You are a professional copywriter and expert translator for CRAZE GmbH, a German toy company. You excel at translating product descriptions from German to English, maintaining the commercial and professional tone while ensuring all technical toy details are accurate. Use clear, engaging language." ;
2026-03-27 11:34:09 +01:00
if ( field === 'longDe' ) {
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. \ n \ n ${ baseContext } ` ;
} 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.
4. Translate every single paragraph.
German Text to Translate:
${ formData . longDe } ` ;
2026-03-27 11:34:09 +01:00
} else {
2026-03-27 13:58:20 +01: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. \ n \ n ${ baseContext } ` ;
2026-03-27 11:34:09 +01:00
}
} else if ( field === 'shortDe' ) {
if ( formData . longDe ) {
2026-03-27 13:58:20 +01:00
prompt = `Create a short version (2-4 sentences max) of the following German product description: \ n \ n ${ formData . longDe } ` ;
2026-03-27 11:34:09 +01:00
} else {
2026-03-27 13:58:20 +01:00
prompt = `Based on the following product details, generate a short commercial description in German (2-4 sentences max). \ n \ n ${ baseContext } ` ;
2026-03-27 11:34:09 +01:00
}
} else if ( field === 'shortEn' ) {
if ( formData . shortDe ) {
2026-03-27 13:58:20 +01:00
prompt = `Translate exactly this short German product description into professional English for the toy market: \ n \ n ${ formData . shortDe } ` ;
} else if ( formData . longEn ) {
prompt = `Create a short version (2-4 sentences max) of the following English product description: \ n \ n ${ formData . longEn } ` ;
2026-03-27 11:34:09 +01:00
} else {
2026-03-27 13:58:20 +01:00
prompt = `Based on the following product details, generate a short commercial description in English (2-4 sentences max). \ 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 () }));
} catch ( err : any ) {
setError ( err . message || 'An error occurred during generation.' );
} finally {
setLoadingField ( null );
}
};
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-03-27 11:34:09 +01:00
onSave ( rowIndex , newRow );
};
2026-03-29 17:30:04 +02:00
const DimensionInput = ({ label , field , placeholder } : { label : string , field : keyof typeof formData , placeholder? : string }) => (
< 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 = { formData [ field ]}
onChange = { e => setFormData ( prev => ({ ... prev , [ field ] : 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 ( field ) ? "border-blue-500 focus:ring-blue-500" : "border-slate-700 focus:border-slate-500"
)}
/>
</ div >
);
2026-03-27 11:34:09 +01:00
const FieldEditor = ({ title , field } : { title : string , field : keyof typeof formData }) => (
< 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 >
2026-03-27 13:58:20 +01:00
< div className = "flex items-center gap-2" >
{(( field === 'longEn' && formData . longDe ) ||
( field === 'shortDe' && formData . longDe ) ||
( field === 'shortEn' && ( formData . shortDe || formData . longEn ))) && (
< div className = "flex items-center gap-1 text-[10px] text-slate-500 italic" >
< Languages className = "w-3 h-3" />
Can translate from existing
</ div >
)}
< button
onClick = {() => handleGenerate ( field )}
disabled = { loadingField !== null }
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"
>
{ loadingField === field ? < Loader2 className = "w-3 h-3 animate-spin" /> : < Sparkles className = "w-3 h-3" />}
{(( field === 'longEn' && formData . longDe ) || ( field === 'shortEn' && formData . shortDe )) ? 'Translate with Gemini' : 'Generate with Gemini' }
</ button >
</ div >
2026-03-27 11:34:09 +01:00
</ div >
< textarea
value = { formData [ field ]}
onChange = { e => setFormData ( prev => ({ ... prev , [ field ] : e . target . value }))}
className = { cn (
"w-full h-32 bg-slate-900 border rounded-md p-3 text-sm text-white focus:outline-none focus:ring-1 transition-colors resize-none" ,
isModified ( field ) ? "border-blue-500 focus:ring-blue-500" : "border-slate-700 focus:border-slate-500 focus:ring-slate-500"
)}
placeholder = { `Enter ${ title } ...` }
/>
</ div >
);
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 >
< p className = "text-sm text-slate-300 line-clamp-2" title = { row [ COLUMNS . DETAILS_DE ]}>{ row [ COLUMNS . DETAILS_DE ] || '-' }</ p >
</ div >
< div className = "col-span-2" >
< span className = "block text-xs text-slate-500 mb-1" > Details ( EN )</ span >
< p className = "text-sm text-slate-300 line-clamp-2" title = { row [ COLUMNS . DETAILS_EN ]}>{ row [ COLUMNS . DETAILS_EN ] || '-' }</ p >
</ 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 >
< DimensionInput label = "Length" field = "innerL" placeholder = "L" />
< DimensionInput label = "Width" field = "innerW" placeholder = "W" />
< DimensionInput label = "Height" field = "innerH" placeholder = "H" />
</ 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 >
< DimensionInput label = "Length" field = "outerL" placeholder = "L" />
< DimensionInput label = "Width" field = "outerW" placeholder = "W" />
< DimensionInput label = "Height" field = "outerH" placeholder = "H" />
</ div >
< div className = "grid grid-cols-2 gap-3" >
< DimensionInput label = "Units per Outer" field = "unitsOuter" />
< DimensionInput label = "MOQ" field = "moq" />
</ div >
2026-03-27 11:34:09 +01:00
</ div >
< FieldEditor title = "Long Description (DE)" field = "longDe" />
< FieldEditor title = "Long Description (EN)" field = "longEn" />
< FieldEditor title = "Short Description (DE)" field = "shortDe" />
< FieldEditor title = "Short Description (EN)" field = "shortEn" />
</ div >
< div className = "p-6 border-t border-slate-700 bg-slate-800/50 flex justify-end gap-3" >
< button
onClick = { onClose }
className = "px-4 py-2 text-sm font-medium text-slate-300 hover:text-white hover:bg-slate-700 rounded-md transition-colors"
>
Cancel
</ button >
< button
2026-03-29 18:26:20 +02:00
onClick = {() => setIsConfirmOpen ( true )}
2026-03-27 11:34:09 +01: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"
>
< Save className = "w-4 h-4" />
Save to Memory
</ 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 }
onConfirm = { executeGenerate }
onCancel = {() => setPendingGeminiField ( null )}
title = "Confirm AI Action"
message = { `Are you sure you want to use Gemini to ${ (( pendingGeminiField === 'longEn' && formData . longDe ) || ( pendingGeminiField === 'shortEn' && formData . shortDe )) ? 'translate' : 'generate' } the ${ pendingGeminiField } field?` }
type = "info"
confirmText = "Start Generation"
/>
2026-03-27 11:34:09 +01:00
</ div >
</>
);
}