|
import { useState, useEffect } from 'react' |
|
import { useTranslation } from 'react-i18next' |
|
import { |
|
Dialog, |
|
DialogContent, |
|
DialogHeader, |
|
DialogTitle, |
|
DialogFooter, |
|
DialogDescription |
|
} from '@/components/ui/Dialog' |
|
import Button from '@/components/ui/Button' |
|
|
|
interface PropertyEditDialogProps { |
|
isOpen: boolean |
|
onClose: () => void |
|
onSave: (value: string) => void |
|
propertyName: string |
|
initialValue: string |
|
isSubmitting?: boolean |
|
} |
|
|
|
|
|
|
|
|
|
|
|
const PropertyEditDialog = ({ |
|
isOpen, |
|
onClose, |
|
onSave, |
|
propertyName, |
|
initialValue, |
|
isSubmitting = false |
|
}: PropertyEditDialogProps) => { |
|
const { t } = useTranslation() |
|
const [value, setValue] = useState('') |
|
|
|
const [error, setError] = useState<string | null>(null) |
|
|
|
|
|
useEffect(() => { |
|
if (isOpen) { |
|
setValue(initialValue) |
|
} |
|
}, [isOpen, initialValue]) |
|
|
|
|
|
const getPropertyNameTranslation = (name: string) => { |
|
const translationKey = `graphPanel.propertiesView.node.propertyNames.${name}` |
|
const translation = t(translationKey) |
|
return translation === translationKey ? name : translation |
|
} |
|
|
|
|
|
const getTextareaConfig = (propertyName: string) => { |
|
switch (propertyName) { |
|
case 'description': |
|
return { |
|
|
|
className: 'max-h-[50vh] min-h-[10em] resize-y', |
|
style: { |
|
height: '70vh', |
|
minHeight: '20em', |
|
resize: 'vertical' as const |
|
} |
|
}; |
|
case 'entity_id': |
|
return { |
|
rows: 2, |
|
className: '', |
|
style: {} |
|
}; |
|
case 'keywords': |
|
return { |
|
rows: 4, |
|
className: '', |
|
style: {} |
|
}; |
|
default: |
|
return { |
|
rows: 5, |
|
className: '', |
|
style: {} |
|
}; |
|
} |
|
}; |
|
|
|
const handleSave = async () => { |
|
if (value.trim() !== '') { |
|
|
|
setError(null) |
|
try { |
|
await onSave(value) |
|
onClose() |
|
} catch (error) { |
|
console.error('Save error:', error) |
|
|
|
setError(typeof error === 'object' && error !== null |
|
? (error as Error).message || t('common.saveFailed') |
|
: t('common.saveFailed')) |
|
} |
|
} |
|
} |
|
|
|
return ( |
|
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}> |
|
<DialogContent className="sm:max-w-md"> |
|
<DialogHeader> |
|
<DialogTitle> |
|
{t('graphPanel.propertiesView.editProperty', { |
|
property: getPropertyNameTranslation(propertyName) |
|
})} |
|
</DialogTitle> |
|
<DialogDescription> |
|
{t('graphPanel.propertiesView.editPropertyDescription')} |
|
</DialogDescription> |
|
</DialogHeader> |
|
|
|
{/* Display error message if save fails */} |
|
{error && ( |
|
<div className="bg-destructive/15 text-destructive px-4 py-2 rounded-md text-sm mt-2"> |
|
{error} |
|
</div> |
|
)} |
|
|
|
{/* Multi-line text input using textarea */} |
|
<div className="grid gap-4 py-4"> |
|
{(() => { |
|
const config = getTextareaConfig(propertyName); |
|
return propertyName === 'description' ? ( |
|
<textarea |
|
value={value} |
|
onChange={(e) => setValue(e.target.value)} |
|
className={`border-input focus-visible:ring-ring flex w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm transition-colors focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 ${config.className}`} |
|
style={config.style} |
|
disabled={isSubmitting} |
|
/> |
|
) : ( |
|
<textarea |
|
value={value} |
|
onChange={(e) => setValue(e.target.value)} |
|
rows={config.rows} |
|
className={`border-input focus-visible:ring-ring flex w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm transition-colors focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 ${config.className}`} |
|
disabled={isSubmitting} |
|
/> |
|
); |
|
})()} |
|
</div> |
|
|
|
<DialogFooter> |
|
<Button |
|
type="button" |
|
variant="outline" |
|
onClick={onClose} |
|
disabled={isSubmitting} |
|
> |
|
{t('common.cancel')} |
|
</Button> |
|
<Button |
|
type="button" |
|
onClick={handleSave} |
|
disabled={isSubmitting} |
|
> |
|
{isSubmitting ? ( |
|
<> |
|
<span className="mr-2"> |
|
<svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> |
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> |
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
|
</svg> |
|
</span> |
|
{t('common.saving')} |
|
</> |
|
) : ( |
|
t('common.save') |
|
)} |
|
</Button> |
|
</DialogFooter> |
|
</DialogContent> |
|
</Dialog> |
|
) |
|
} |
|
|
|
export default PropertyEditDialog |
|
|