Files
fuzzygraph/frontend/src/components/EntityDialog.tsx
T
dataforge c9fd4aa84c feat: enrichers, panel de ingest y menu contextual en el grafo
- Añade enricher.go + directorio enrichers/ para enriquecer entidades con fuentes externas.
- Nuevos componentes frontend: IngestPanel (panel de ingesta de datos) y NodeContextMenu (menu contextual sobre nodos del grafo).
- Retira SearchBar y lib/utils.ts; la busqueda se integra dentro de los paneles existentes.
- Ajusta tipos (types.go, types.ts, wailsjs/go) y theming (postcss + app.css + Mantine).
- Actualiza app.go y wails.json para exponer las nuevas capacidades.
- Añade directorio projects/ con estado inicial.
- Rebuild del frontend (dist actualizado).
2026-04-13 23:32:55 +02:00

151 lines
4.7 KiB
TypeScript

import { useState, useEffect } from 'react'
import { Stack, Group, TextInput, Textarea, Text } from '@mantine/core'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, SimpleSelect, Button } from '@fn_library'
import type { Entity, EntityTypePreset, EntityInput } from '../types'
interface Props {
presets: EntityTypePreset[]
entity: Entity | null
onSubmit: (input: EntityInput) => void
onClose: () => void
}
export function EntityDialog({ presets, entity, onSubmit, onClose }: Props) {
const [name, setName] = useState(entity?.name ?? '')
const [typeRef, setTypeRef] = useState(entity?.type_ref ?? presets[0]?.type_ref ?? '')
const [description, setDescription] = useState(entity?.description ?? '')
const [notes, setNotes] = useState(entity?.notes ?? '')
const [tagsStr, setTagsStr] = useState((entity?.tags ?? []).join(', '))
const [metadata, setMetadata] = useState<Record<string, string>>(() => {
const m: Record<string, string> = {}
if (entity?.metadata) {
for (const [k, v] of Object.entries(entity.metadata)) {
m[k] = String(v ?? '')
}
}
return m
})
const currentPreset = presets.find(p => p.type_ref === typeRef)
const metadataFields = currentPreset?.metadata_fields ?? []
useEffect(() => {
if (!entity) {
const m: Record<string, string> = {}
for (const f of metadataFields) {
m[f] = metadata[f] ?? ''
}
setMetadata(m)
}
}, [typeRef]) // eslint-disable-line react-hooks/exhaustive-deps
const handleSubmit = () => {
const cleanMeta: Record<string, unknown> = {}
for (const [k, v] of Object.entries(metadata)) {
if (v.trim()) {
const num = Number(v)
if (!isNaN(num) && v.trim() !== '') {
cleanMeta[k] = num
} else if (v === 'true') {
cleanMeta[k] = true
} else if (v === 'false') {
cleanMeta[k] = false
} else {
cleanMeta[k] = v.trim()
}
}
}
onSubmit({
name: name.trim(),
type_ref: typeRef,
description: description.trim(),
tags: tagsStr.split(',').map(t => t.trim()).filter(Boolean),
metadata: cleanMeta,
notes: notes.trim(),
})
}
return (
<Dialog open onOpenChange={(open: boolean) => { if (!open) onClose() }}>
<DialogContent>
<DialogHeader>
<DialogTitle>{entity ? 'Edit Entity' : 'New Entity'}</DialogTitle>
</DialogHeader>
<Stack gap="sm">
<TextInput
label="Name"
value={name}
onChange={e => setName(e.currentTarget.value)}
size="sm"
/>
<div>
<Text size="sm" fw={500} mb={4}>Type</Text>
<SimpleSelect
value={typeRef}
onValueChange={setTypeRef}
options={presets.map(p => ({ value: p.type_ref, label: p.label }))}
/>
</div>
<TextInput
label="Description"
value={description}
onChange={e => setDescription(e.currentTarget.value)}
size="sm"
/>
<TextInput
label="Tags (comma separated)"
value={tagsStr}
onChange={e => setTagsStr(e.currentTarget.value)}
placeholder="osint, high-risk"
size="sm"
/>
{metadataFields.length > 0 && (
<div>
<Text size="xs" fw={600} c="dimmed" mb="xs">
Metadata ({currentPreset?.label})
</Text>
<Stack gap="xs">
{metadataFields.map(field => (
<Group key={field} gap="sm" align="center">
<Text size="xs" c="dimmed" w={112} ta="right">{field}</Text>
<TextInput
value={metadata[field] ?? ''}
onChange={e => setMetadata(prev => ({ ...prev, [field]: e.currentTarget.value }))}
size="xs"
flex={1}
/>
</Group>
))}
</Stack>
</div>
)}
<Textarea
label="Notes"
value={notes}
onChange={e => setNotes(e.currentTarget.value)}
rows={3}
placeholder="Operational notes..."
size="sm"
/>
</Stack>
<DialogFooter>
<Group justify="flex-end" gap="sm" mt="md">
<Button variant="secondary" onClick={onClose}>Cancel</Button>
<Button onClick={handleSubmit} disabled={!name.trim()}>
{entity ? 'Update' : 'Create'}
</Button>
</Group>
</DialogFooter>
</DialogContent>
</Dialog>
)
}