feat(frontend): streamline task creation and preview workflows

- remove prompt and preview generation from task creation

- create tasks as inactive and route directly to edit page

- add generated message history UX to edit task

- update entity/task views and related test coverage
This commit is contained in:
2026-03-27 02:23:56 -03:00
parent a83ea85857
commit f2a16b5cf6
10 changed files with 430 additions and 222 deletions

View File

@@ -1,10 +1,24 @@
import { useQuery } from '@tanstack/react-query'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import { getEntity } from '../api/entitiesApi'
import { getEntity, updateEntity, VirtualEntityCreateDto, VirtualEntityResponse } from '../api/entitiesApi'
import { getEmailLookbackLabel, getTasksByEntity } from '../api/tasksApi'
function toEntityForm(entity: VirtualEntityResponse): VirtualEntityCreateDto {
return {
name: entity.name,
email: entity.email,
jobTitle: entity.jobTitle,
personality: entity.personality ?? '',
contextWindowDays: entity.contextWindowDays,
}
}
export default function EntityDetailPage() {
const { entityId = '' } = useParams()
const queryClient = useQueryClient()
const [dialogOpen, setDialogOpen] = useState(false)
const [form, setForm] = useState<VirtualEntityCreateDto | null>(null)
const { data: entity, isLoading: isLoadingEntity } = useQuery({
queryKey: ['entity', entityId],
@@ -18,6 +32,18 @@ export default function EntityDetailPage() {
enabled: Boolean(entityId),
})
const updateMutation = useMutation({
mutationFn: (data: VirtualEntityCreateDto) => updateEntity(entityId, data),
onSuccess: async () => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ['entity', entityId] }),
queryClient.invalidateQueries({ queryKey: ['entities'] }),
])
setDialogOpen(false)
setForm(null)
},
})
if (!entityId) {
return <div className="p-8 text-sm text-slate-300">Entity identifier is missing.</div>
}
@@ -49,12 +75,24 @@ export default function EntityDetailPage() {
{entity.jobTitle} - {entity.email}
</p>
</div>
<Link
to={`/entities/${entityId}/tasks/new`}
className="rounded-md bg-cyan-500 px-4 py-2 text-sm font-semibold text-slate-950 hover:bg-cyan-400"
>
New Task
</Link>
<div className="flex items-center gap-3">
<button
type="button"
onClick={() => {
setForm(toEntityForm(entity))
setDialogOpen(true)
}}
className="rounded-md border border-slate-700 px-4 py-2 text-sm font-semibold text-slate-100 hover:border-cyan-500 hover:text-cyan-300"
>
Edit Entity
</button>
<Link
to={`/entities/${entityId}/tasks/new`}
className="rounded-md bg-cyan-500 px-4 py-2 text-sm font-semibold text-slate-950 hover:bg-cyan-400"
>
New Task
</Link>
</div>
</div>
<section>
@@ -100,6 +138,93 @@ export default function EntityDetailPage() {
)}
</ul>
</section>
{dialogOpen && form && (
<div
role="dialog"
aria-modal="true"
aria-label="Edit Entity"
className="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70"
>
<div className="w-full max-w-md rounded-lg border border-slate-800 bg-slate-950 p-6 shadow-lg">
<h2 className="mb-4 text-lg font-semibold text-slate-100">Edit Entity</h2>
<form
onSubmit={(e) => {
e.preventDefault()
updateMutation.mutate(form)
}}
className="space-y-3"
>
<div>
<label htmlFor="entity-name" className="mb-1 block text-sm font-medium text-slate-200">
Entity Name
</label>
<input
id="entity-name"
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
className="block w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-slate-100"
required
/>
</div>
<div>
<label htmlFor="entity-email" className="mb-1 block text-sm font-medium text-slate-200">
Sender Email
</label>
<input
id="entity-email"
type="email"
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
className="block w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-slate-100"
required
/>
</div>
<div>
<label htmlFor="entity-job-title" className="mb-1 block text-sm font-medium text-slate-200">
Job Title
</label>
<input
id="entity-job-title"
value={form.jobTitle}
onChange={(e) => setForm({ ...form, jobTitle: e.target.value })}
className="block w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-slate-100"
required
/>
</div>
<div>
<label htmlFor="entity-personality" className="mb-1 block text-sm font-medium text-slate-200">
Personality Notes
</label>
<textarea
id="entity-personality"
value={form.personality}
onChange={(e) => setForm({ ...form, personality: e.target.value })}
className="block w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-slate-100"
/>
</div>
<div className="flex justify-end gap-2 pt-2">
<button
type="button"
onClick={() => {
setDialogOpen(false)
setForm(null)
}}
className="rounded border border-slate-700 px-4 py-2 text-sm text-slate-200"
>
Cancel
</button>
<button
type="submit"
className="rounded bg-cyan-500 px-4 py-2 text-sm font-medium text-slate-950 hover:bg-cyan-400"
>
Save
</button>
</div>
</form>
</div>
</div>
)}
</div>
)
}