mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 17:05:24 +02:00
187 lines
9.0 KiB
TypeScript
187 lines
9.0 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { ExcelRow, COLUMNS } from '../types';
|
|
import { X, Sparkles, Save, Loader2, Languages } from 'lucide-react';
|
|
import { generateGemini } from '../services/gemini';
|
|
import { cn } from '../lib/utils';
|
|
|
|
interface EditPanelProps {
|
|
row: ExcelRow;
|
|
rowIndex: number;
|
|
onSave: (rowIndex: number, updatedRow: ExcelRow) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export function EditPanel({ row, rowIndex, onSave, onClose }: EditPanelProps) {
|
|
const [formData, setFormData] = useState({
|
|
longDe: row[COLUMNS.LONG_DE] || '',
|
|
longEn: row[COLUMNS.LONG_EN] || '',
|
|
shortDe: row[COLUMNS.SHORT_DE] || '',
|
|
shortEn: row[COLUMNS.SHORT_EN] || '',
|
|
});
|
|
|
|
const [loadingField, setLoadingField] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const isModified = (field: keyof typeof formData) => {
|
|
const colMap = {
|
|
longDe: COLUMNS.LONG_DE,
|
|
longEn: COLUMNS.LONG_EN,
|
|
shortDe: COLUMNS.SHORT_DE,
|
|
shortEn: COLUMNS.SHORT_EN,
|
|
};
|
|
return formData[field] !== (row[colMap[field]] || '');
|
|
};
|
|
|
|
const handleGenerate = async (field: keyof typeof formData) => {
|
|
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'}`;
|
|
|
|
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.";
|
|
|
|
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) {
|
|
prompt = `Translate exactly this German product description into professional English for the toy market. Ensure a natural flow and high-quality vocabulary:\n\n${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.\n\n${baseContext}`;
|
|
}
|
|
} else if (field === 'shortDe') {
|
|
if (formData.longDe) {
|
|
prompt = `Create a short version (2-4 sentences max) of the following German product description:\n\n${formData.longDe}`;
|
|
} else {
|
|
prompt = `Based on the following product details, generate a short commercial description in German (2-4 sentences max).\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:\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}`;
|
|
} else {
|
|
prompt = `Based on the following product details, generate a short commercial description in English (2-4 sentences max).\n\n${baseContext}`;
|
|
}
|
|
}
|
|
|
|
const generatedText = await generateGemini(prompt, systemPrompt);
|
|
setFormData(prev => ({ ...prev, [field]: generatedText.trim() }));
|
|
} catch (err: any) {
|
|
setError(err.message || 'An error occurred during generation.');
|
|
} finally {
|
|
setLoadingField(null);
|
|
}
|
|
};
|
|
|
|
const handleSave = () => {
|
|
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;
|
|
onSave(rowIndex, newRow);
|
|
};
|
|
|
|
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>
|
|
<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>
|
|
</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>
|
|
</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
|
|
onClick={handleSave}
|
|
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>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|