diff --git a/frontend/src/__tests__/api/authApi.test.ts b/frontend/src/__tests__/api/authApi.test.ts index 44d2ce3..d2c7d78 100644 --- a/frontend/src/__tests__/api/authApi.test.ts +++ b/frontend/src/__tests__/api/authApi.test.ts @@ -9,7 +9,7 @@ vi.mock('@/api/apiClient', () => ({ }, })) -const mockedApiClient = apiClient as { +const mockedApiClient = apiClient as unknown as { post: ReturnType get: ReturnType } diff --git a/frontend/src/__tests__/api/entitiesApi.test.ts b/frontend/src/__tests__/api/entitiesApi.test.ts index 6d7988b..ae0c47b 100644 --- a/frontend/src/__tests__/api/entitiesApi.test.ts +++ b/frontend/src/__tests__/api/entitiesApi.test.ts @@ -17,7 +17,7 @@ vi.mock('@/api/apiClient', () => ({ }, })) -const mockedApiClient = apiClient as { +const mockedApiClient = apiClient as unknown as { get: ReturnType post: ReturnType put: ReturnType diff --git a/frontend/src/__tests__/api/logsApi.test.ts b/frontend/src/__tests__/api/logsApi.test.ts index 0ab87c7..fa17c5e 100644 --- a/frontend/src/__tests__/api/logsApi.test.ts +++ b/frontend/src/__tests__/api/logsApi.test.ts @@ -8,7 +8,7 @@ vi.mock('@/api/apiClient', () => ({ }, })) -const mockedApiClient = apiClient as { +const mockedApiClient = apiClient as unknown as { get: ReturnType } diff --git a/frontend/src/api/logsApi.ts b/frontend/src/api/logsApi.ts index 9a8eda5..005f445 100644 --- a/frontend/src/api/logsApi.ts +++ b/frontend/src/api/logsApi.ts @@ -5,6 +5,7 @@ export type DispatchStatus = 'PENDING' | 'SENT' | 'FAILED' export interface DispatchLogResponse { id: string entityId: string + entityName: string promptSent: string aiResponse: string emailSubject: string diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 9f586ce..027c885 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -1,11 +1,38 @@ +import { useQuery } from '@tanstack/react-query' +import { getEntities } from '../api/entitiesApi' +import { getLogs } from '../api/logsApi' + export default function DashboardPage() { const appVersion = __APP_VERSION__ + const { data: entities = [] } = useQuery({ queryKey: ['entities'], queryFn: getEntities }) + const { data: logs = [] } = useQuery({ queryKey: ['logs'], queryFn: getLogs }) + + const activeCount = entities.filter((e) => e.active).length return (

Dashboard

-

Dashboard — coming in Step 11.

Version {appVersion}

+
+
+

Active Entities

+

{activeCount} active {activeCount === 1 ? 'entity' : 'entities'}

+
+
+
+

Recent Dispatches

+
    + {logs.slice(0, 10).map((log) => ( +
  • + {log.emailSubject} + {log.entityName} +
  • + ))} + {logs.length === 0 && ( +
  • No dispatches yet.
  • + )} +
+
) } diff --git a/frontend/src/pages/EntitiesPage.tsx b/frontend/src/pages/EntitiesPage.tsx new file mode 100644 index 0000000..23900f0 --- /dev/null +++ b/frontend/src/pages/EntitiesPage.tsx @@ -0,0 +1,150 @@ +import { useState } from 'react' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { + getEntities, + createEntity, + deleteEntity, + VirtualEntityCreateDto, +} from '../api/entitiesApi' + +export default function EntitiesPage() { + const queryClient = useQueryClient() + const { data: entities = [] } = useQuery({ queryKey: ['entities'], queryFn: getEntities }) + const [dialogOpen, setDialogOpen] = useState(false) + const [form, setForm] = useState({ + name: '', + email: '', + jobTitle: '', + personality: '', + scheduleCron: '', + contextWindowDays: 3, + }) + + const createMutation = useMutation({ + mutationFn: createEntity, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['entities'] }) + setDialogOpen(false) + setForm({ name: '', email: '', jobTitle: '', personality: '', scheduleCron: '', contextWindowDays: 3 }) + }, + }) + + const deleteMutation = useMutation({ + mutationFn: (id: string) => deleteEntity(id), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['entities'] }), + }) + + return ( +
+
+

Virtual Entities

+ +
+ +
    + {entities.map((entity) => ( +
  • +
    +

    {entity.name}

    +

    {entity.jobTitle} — {entity.email}

    +
    + +
  • + ))} + {entities.length === 0 && ( +
  • No entities yet.
  • + )} +
+ + {dialogOpen && ( +
+
+

New Entity

+
{ + e.preventDefault() + createMutation.mutate(form) + }} + className="space-y-3" + > + setForm({ ...form, name: e.target.value })} + className="block w-full rounded border border-gray-300 px-3 py-2 text-sm" + required + /> + setForm({ ...form, email: e.target.value })} + className="block w-full rounded border border-gray-300 px-3 py-2 text-sm" + required + /> + setForm({ ...form, jobTitle: e.target.value })} + className="block w-full rounded border border-gray-300 px-3 py-2 text-sm" + required + /> +