);
export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: EditPanelProps) {
const COLUMNS = useColumns();
const [formData, setFormData] = useState({
longDe: row[COLUMNS.LONG_DE] || '',
longEn: row[COLUMNS.LONG_EN] || '',
shortDe: row[COLUMNS.SHORT_DE] || '',
shortEn: row[COLUMNS.SHORT_EN] || '',
detailsDe: row[COLUMNS.DETAILS_DE] || '',
detailsEn: row[COLUMNS.DETAILS_EN] || '',
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]) : '',
productType: row[COLUMNS.CATEGORIZATION_CODE] !== undefined && row[COLUMNS.CATEGORIZATION_CODE] !== null ? String(row[COLUMNS.CATEGORIZATION_CODE]) : '',
itemToLogistic: row[COLUMNS.ITEM_TO_LOGISTIC] !== undefined && row[COLUMNS.ITEM_TO_LOGISTIC] !== null ? String(row[COLUMNS.ITEM_TO_LOGISTIC]) : '',
});
const [loadingField, setLoadingField] = useState(null);
const [error, setError] = useState(null);
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
const [pendingGeminiField, setPendingGeminiField] = useState(null);
const [generatedFields, setGeneratedFields] = useState>(new Set());
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' });
const isModified = (field: keyof typeof formData) => {
const colMap: Record = {
longDe: COLUMNS.LONG_DE,
longEn: COLUMNS.LONG_EN,
shortDe: COLUMNS.SHORT_DE,
shortEn: COLUMNS.SHORT_EN,
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,
detailsDe: COLUMNS.DETAILS_DE,
detailsEn: COLUMNS.DETAILS_EN,
productType: COLUMNS.CATEGORIZATION_CODE,
itemToLogistic: COLUMNS.ITEM_TO_LOGISTIC,
};
const colIndex = (colMap as Record)[field as string];
if (colIndex === undefined) return false;
return formData[field] !== (row[colIndex] !== undefined && row[colIndex] !== null ? String(row[colIndex]) : '');
};
const handleGenerate = (field: keyof typeof formData) => {
console.log('[EditPanel] handleGenerate called for field:', field);
setError(null); // Clear any previous error
setPendingGeminiField(field);
};
const executeGenerate = async () => {
if (!pendingGeminiField) {
console.warn('[EditPanel] executeGenerate called but no field pending');
return;
}
const field = pendingGeminiField;
console.log('[EditPanel] Starting generation for field:', field);
setLoadingField(field);
setError(null);
try {
let prompt = '';
const baseContext = `Article Name: ${row[COLUMNS.ARTICLE_NAME]}\nArticle Details (EN): ${formData.detailsEn || 'N/A'}\nArticle Details (DE): ${formData.detailsDe || 'N/A'}`;
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.";
if (field === 'longDe') {
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.
5. Translate ALL text, including titles or phrases in ALL CAPS, and keep them in ALL CAPS in the translation.
6. ENSURE the translation is complete and does not cut off. The final sentence must be fully finished.
English Text to Translate:
${formData.longEn}`;
} else {
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}`;
}
} else if (field === 'longEn') {
if (formData.longDe) {
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 into natural, commercial English for toy buyers.
5. Translate ALL text, including titles or phrases in ALL CAPS, and keep them in ALL CAPS in the translation.
6. ENSURE the translation is complete and does not cut off. The final sentence must be fully finished.
German Text to Translate:
${formData.longDe}`;
} else {
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}`;
}
} else if (field === 'shortDe') {
if (formData.shortEn) {
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}`;
} else if (formData.longDe) {
prompt = `Create a concise summary of the following German product description.
CRITICAL: The summary MUST be between 250 and 450 characters long.
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}`;
} else {
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}`;
}
} else if (field === 'shortEn') {
if (formData.shortDe) {
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}`;
} else if (formData.longEn) {
prompt = `Create a concise summary of the following English product description.
CRITICAL: The summary MUST be between 250 and 450 characters long.
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}`;
} else {
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}`;
}
}
const generatedText = await generateGemini(prompt, systemPrompt);
setFormData(prev => ({ ...prev, [field]: generatedText.trim() }));
setGeneratedFields(prev => new Set(prev).add(field));
// Focus the textarea so user can edit immediately after AI generation
setTimeout(() => {
const textarea = document.getElementById(`field-${field}`);
if (textarea) textarea.focus();
}, 100);
setPendingGeminiField(null);
} catch (err: any) {
setError(err.message || 'An error occurred during generation.');
setPendingGeminiField(null); // Close modal on error so error message is visible in panel
} finally {
setLoadingField(null);
}
};
const handleSave = () => {
setIsConfirmOpen(false);
const hasModifications = Object.keys(formData).some(k => isModified(k as keyof typeof formData));
if (hasModifications) {
onCaptureState(`Updated product ${row[COLUMNS.ARTICLE_NO]}`);
}
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;
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;
newRow[COLUMNS.DETAILS_DE] = formData.detailsDe;
newRow[COLUMNS.DETAILS_EN] = formData.detailsEn;
newRow[COLUMNS.CATEGORIZATION_CODE] = formData.productType;
newRow[COLUMNS.ITEM_TO_LOGISTIC] = formData.itemToLogistic;
onSave(rowIndex, newRow);
};
const handleInputKeyDown = (e: React.KeyboardEvent) => {
e.stopPropagation();
};
return (
<>