feat: add created_by_email field to document schema and update related components for improved user information display

This commit is contained in:
Anish Sarkar 2026-02-21 23:41:00 +05:30
parent 12b119be59
commit f3652ad7cf
7 changed files with 65 additions and 18 deletions

View file

@ -373,10 +373,11 @@ async def read_documents(
# Convert database objects to API-friendly format # Convert database objects to API-friendly format
api_documents = [] api_documents = []
for doc in db_documents: for doc in db_documents:
# Get user name (display_name or email fallback)
created_by_name = None created_by_name = None
created_by_email = None
if doc.created_by: if doc.created_by:
created_by_name = doc.created_by.display_name or doc.created_by.email created_by_name = doc.created_by.display_name
created_by_email = doc.created_by.email
# Parse status from JSONB # Parse status from JSONB
status_data = None status_data = None
@ -400,6 +401,7 @@ async def read_documents(
search_space_id=doc.search_space_id, search_space_id=doc.search_space_id,
created_by_id=doc.created_by_id, created_by_id=doc.created_by_id,
created_by_name=created_by_name, created_by_name=created_by_name,
created_by_email=created_by_email,
status=status_data, status=status_data,
) )
) )
@ -528,10 +530,11 @@ async def search_documents(
# Convert database objects to API-friendly format # Convert database objects to API-friendly format
api_documents = [] api_documents = []
for doc in db_documents: for doc in db_documents:
# Get user name (display_name or email fallback)
created_by_name = None created_by_name = None
created_by_email = None
if doc.created_by: if doc.created_by:
created_by_name = doc.created_by.display_name or doc.created_by.email created_by_name = doc.created_by.display_name
created_by_email = doc.created_by.email
# Parse status from JSONB # Parse status from JSONB
status_data = None status_data = None
@ -555,6 +558,7 @@ async def search_documents(
search_space_id=doc.search_space_id, search_space_id=doc.search_space_id,
created_by_id=doc.created_by_id, created_by_id=doc.created_by_id,
created_by_name=created_by_name, created_by_name=created_by_name,
created_by_email=created_by_email,
status=status_data, status=status_data,
) )
) )

View file

@ -60,9 +60,8 @@ class DocumentRead(BaseModel):
updated_at: datetime | None updated_at: datetime | None
search_space_id: int search_space_id: int
created_by_id: UUID | None = None # User who created/uploaded this document created_by_id: UUID | None = None # User who created/uploaded this document
created_by_name: str | None = ( created_by_name: str | None = None
None # Display name or email of the user who created this document created_by_email: str | None = None
)
status: DocumentStatusSchema | None = ( status: DocumentStatusSchema | None = (
None # Processing status (ready, processing, failed) None # Processing status (ready, processing, failed)
) )

View file

@ -453,7 +453,7 @@ export function DocumentsTableShell({
) : error ? ( ) : error ? (
<div className="flex h-[50vh] w-full items-center justify-center"> <div className="flex h-[50vh] w-full items-center justify-center">
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<AlertCircle className="h-8 w-8 text-destructive/60" /> <AlertCircle className="h-8 w-8 text-destructive" />
<p className="text-sm text-destructive">{t("error_loading")}</p> <p className="text-sm text-destructive">{t("error_loading")}</p>
</div> </div>
</div> </div>
@ -627,11 +627,30 @@ export function DocumentsTableShell({
<DocumentTypeChip type={doc.document_type} /> <DocumentTypeChip type={doc.document_type} />
</TableCell> </TableCell>
)} )}
{columnVisibility.created_by && ( {columnVisibility.created_by && (
<TableCell className="w-36 py-2.5 text-sm text-foreground truncate border-r border-border/40"> <TableCell className="w-36 py-2.5 text-sm text-foreground truncate border-r border-border/40">
{doc.created_by_name || "—"} {doc.created_by_name ? (
</TableCell> doc.created_by_email ? (
)} <Tooltip>
<TooltipTrigger asChild>
<span className="cursor-default truncate block">
{doc.created_by_name}
</span>
</TooltipTrigger>
<TooltipContent side="top" align="start">
{doc.created_by_email}
</TooltipContent>
</Tooltip>
) : (
<span className="truncate block">{doc.created_by_name}</span>
)
) : (
<span className="truncate block">
{doc.created_by_email || "—"}
</span>
)}
</TableCell>
)}
{columnVisibility.created_at && ( {columnVisibility.created_at && (
<TableCell className="w-32 py-2.5 text-sm text-foreground border-r border-border/40"> <TableCell className="w-32 py-2.5 text-sm text-foreground border-r border-border/40">
<Tooltip> <Tooltip>

View file

@ -16,6 +16,7 @@ export type Document = {
search_space_id: number; search_space_id: number;
created_by_id?: string | null; created_by_id?: string | null;
created_by_name?: string | null; created_by_name?: string | null;
created_by_email?: string | null;
status?: DocumentStatus; status?: DocumentStatus;
}; };

View file

@ -115,6 +115,7 @@ export default function DocumentsTable() {
title: item.title, title: item.title,
created_by_id: item.created_by_id ?? null, created_by_id: item.created_by_id ?? null,
created_by_name: item.created_by_name ?? null, created_by_name: item.created_by_name ?? null,
created_by_email: item.created_by_email ?? null,
created_at: item.created_at, created_at: item.created_at,
status: ( status: (
item as { item as {

View file

@ -44,6 +44,7 @@ export const document = z.object({
search_space_id: z.number(), search_space_id: z.number(),
created_by_id: z.string().nullable().optional(), created_by_id: z.string().nullable().optional(),
created_by_name: z.string().nullable().optional(), created_by_name: z.string().nullable().optional(),
created_by_email: z.string().nullable().optional(),
}); });
export const extensionDocumentContent = z.object({ export const extensionDocumentContent = z.object({

View file

@ -26,7 +26,7 @@ interface DocumentElectric {
status: DocumentStatusType | null; status: DocumentStatusType | null;
} }
// Document for display (with resolved user name) // Document for display (with resolved user name and email)
export interface DocumentDisplay { export interface DocumentDisplay {
id: number; id: number;
search_space_id: number; search_space_id: number;
@ -34,6 +34,7 @@ export interface DocumentDisplay {
title: string; title: string;
created_by_id: string | null; created_by_id: string | null;
created_by_name: string | null; created_by_name: string | null;
created_by_email: string | null;
created_at: string; created_at: string;
status: DocumentStatusType; status: DocumentStatusType;
} }
@ -94,8 +95,9 @@ export function useDocuments(
// Track if initial API load is complete (source of truth) // Track if initial API load is complete (source of truth)
const apiLoadedRef = useRef(false); const apiLoadedRef = useRef(false);
// User cache: userId → displayName // User cache: userId → displayName / email
const userCacheRef = useRef<Map<string, string>>(new Map()); const userCacheRef = useRef<Map<string, string>>(new Map());
const emailCacheRef = useRef<Map<string, string>>(new Map());
// Electric sync refs // Electric sync refs
const syncHandleRef = useRef<SyncHandle | null>(null); const syncHandleRef = useRef<SyncHandle | null>(null);
@ -119,10 +121,21 @@ export function useDocuments(
// Populate user cache from API response // Populate user cache from API response
const populateUserCache = useCallback( const populateUserCache = useCallback(
(items: Array<{ created_by_id?: string | null; created_by_name?: string | null }>) => { (
items: Array<{
created_by_id?: string | null;
created_by_name?: string | null;
created_by_email?: string | null;
}>
) => {
for (const item of items) { for (const item of items) {
if (item.created_by_id && item.created_by_name) { if (item.created_by_id) {
userCacheRef.current.set(item.created_by_id, item.created_by_name); if (item.created_by_name) {
userCacheRef.current.set(item.created_by_id, item.created_by_name);
}
if (item.created_by_email) {
emailCacheRef.current.set(item.created_by_id, item.created_by_email);
}
} }
} }
}, },
@ -138,6 +151,7 @@ export function useDocuments(
title: string; title: string;
created_by_id?: string | null; created_by_id?: string | null;
created_by_name?: string | null; created_by_name?: string | null;
created_by_email?: string | null;
created_at: string; created_at: string;
status?: DocumentStatusType | null; status?: DocumentStatusType | null;
}): DocumentDisplay => ({ }): DocumentDisplay => ({
@ -147,6 +161,7 @@ export function useDocuments(
title: item.title, title: item.title,
created_by_id: item.created_by_id ?? null, created_by_id: item.created_by_id ?? null,
created_by_name: item.created_by_name ?? null, created_by_name: item.created_by_name ?? null,
created_by_email: item.created_by_email ?? null,
created_at: item.created_at, created_at: item.created_at,
status: item.status ?? { state: "ready" }, status: item.status ?? { state: "ready" },
}), }),
@ -160,6 +175,9 @@ export function useDocuments(
created_by_name: doc.created_by_id created_by_name: doc.created_by_id
? (userCacheRef.current.get(doc.created_by_id) ?? null) ? (userCacheRef.current.get(doc.created_by_id) ?? null)
: null, : null,
created_by_email: doc.created_by_id
? (emailCacheRef.current.get(doc.created_by_id) ?? null)
: null,
status: doc.status ?? { state: "ready" }, status: doc.status ?? { state: "ready" },
}), }),
[] []
@ -351,6 +369,9 @@ export function useDocuments(
created_by_name: doc.created_by_id created_by_name: doc.created_by_id
? (userCacheRef.current.get(doc.created_by_id) ?? null) ? (userCacheRef.current.get(doc.created_by_id) ?? null)
: null, : null,
created_by_email: doc.created_by_id
? (emailCacheRef.current.get(doc.created_by_id) ?? null)
: null,
})) }))
); );
} }
@ -455,6 +476,7 @@ export function useDocuments(
setAllDocuments([]); setAllDocuments([]);
apiLoadedRef.current = false; apiLoadedRef.current = false;
userCacheRef.current.clear(); userCacheRef.current.clear();
emailCacheRef.current.clear();
} }
prevSearchSpaceIdRef.current = searchSpaceId; prevSearchSpaceIdRef.current = searchSpaceId;
}, [searchSpaceId]); }, [searchSpaceId]);