Просмотр исходного кода

fix: document display and system fields in view editor

- Transform API response in Documents.tsx (id→_id, nested data→flat)
- Fix system collection prefixing (collections starting with _ not prefixed)
- Add system fields UI in view editor (ID, created/updated at/by)
- System fields can be toggled on/off and saved with view schema
Fszontagh 6 месяцев назад
Родитель
Сommit
928fb006e8
3 измененных файлов с 113 добавлено и 7 удалено
  1. 4 0
      webserver/src/document_service.cpp
  2. 31 4
      webui/src/pages/Documents.tsx
  3. 78 3
      webui/src/pages/Views.tsx

+ 4 - 0
webserver/src/document_service.cpp

@@ -239,6 +239,10 @@ auto DocumentService::DeleteDocument(const std::string& workspace_id, const std:
 
 
 auto DocumentService::GetInternalCollectionName(const std::string& workspace_id,
 auto DocumentService::GetInternalCollectionName(const std::string& workspace_id,
                                                  const std::string& collection) -> std::string {
                                                  const std::string& collection) -> std::string {
+    // System collections (starting with _) are global and not prefixed
+    if (!collection.empty() && collection[0] == '_') {
+        return collection;
+    }
     return std::string(kCollectionPrefix) + workspace_id + "_" + collection;
     return std::string(kCollectionPrefix) + workspace_id + "_" + collection;
 }
 }
 
 

+ 31 - 4
webui/src/pages/Documents.tsx

@@ -53,11 +53,36 @@ interface Document {
   [key: string]: unknown
   [key: string]: unknown
 }
 }
 
 
+// Raw API response format
+interface RawDocument {
+  id: string
+  collection: string
+  workspace_id: string
+  data: Record<string, unknown>
+  created_at: string
+  updated_at: string
+  version: number
+}
+
 interface DocumentsResponse {
 interface DocumentsResponse {
-  documents: Document[]
+  documents: RawDocument[]
   total_count: number
   total_count: number
-  limit: number
-  offset: number
+  limit?: number
+  offset?: number
+}
+
+// Transform API response to frontend format
+function transformDocument(raw: RawDocument): Document {
+  return {
+    _id: raw.id,
+    _created_at: raw.created_at,
+    _updated_at: raw.updated_at,
+    _created_by: raw.data._created_by as string | undefined,
+    _updated_by: raw.data._updated_by as string | undefined,
+    _created_by_name: raw.data._created_by_name as string | undefined,
+    _updated_by_name: raw.data._updated_by_name as string | undefined,
+    ...raw.data,
+  }
 }
 }
 
 
 // Get display columns from schema
 // Get display columns from schema
@@ -632,7 +657,9 @@ function Documents() {
         `/workspaces/${currentWorkspace.id}/collections/${collectionName}/documents?${params}`
         `/workspaces/${currentWorkspace.id}/collections/${collectionName}/documents?${params}`
       )
       )
 
 
-      setDocuments(response.documents || [])
+      // Transform raw documents to frontend format
+      const transformedDocs = (response.documents || []).map(transformDocument)
+      setDocuments(transformedDocs)
       setTotalCount(response.total_count || 0)
       setTotalCount(response.total_count || 0)
     } catch (err) {
     } catch (err) {
       setError(err instanceof Error ? err.message : 'Failed to fetch documents')
       setError(err instanceof Error ? err.message : 'Failed to fetch documents')

+ 78 - 3
webui/src/pages/Views.tsx

@@ -67,6 +67,15 @@ const getWidgetsForType = (fieldType: string) => {
   return WIDGET_TYPES_BY_FIELD[fieldType] || [{ value: '', label: 'Auto' }]
   return WIDGET_TYPES_BY_FIELD[fieldType] || [{ value: '', label: 'Auto' }]
 }
 }
 
 
+// Built-in system fields that can be displayed in views
+const SYSTEM_FIELDS = [
+  { name: '_id', label: 'Document ID', type: 'text', description: 'Unique identifier' },
+  { name: '_created_at', label: 'Created At', type: 'datetime', description: 'Creation timestamp' },
+  { name: '_updated_at', label: 'Updated At', type: 'datetime', description: 'Last update timestamp' },
+  { name: '_created_by_name', label: 'Created By', type: 'text', description: 'Name of user who created' },
+  { name: '_updated_by_name', label: 'Updated By', type: 'text', description: 'Name of user who last updated' },
+] as const
+
 interface SchemaField {
 interface SchemaField {
   _id: string // Client-side ID for React keys
   _id: string // Client-side ID for React keys
   name: string
   name: string
@@ -395,6 +404,18 @@ function ViewModal({
     (view?.settings?.quick_edit_fields as string[]) || []
     (view?.settings?.quick_edit_fields as string[]) || []
   )
   )
 
 
+  // System fields state - built-in document metadata fields
+  const [enabledSystemFields, setEnabledSystemFields] = useState<string[]>(() => {
+    if (view?.schema?.fields) {
+      // Check which system fields are already in the schema
+      const systemFieldNames = ['_id', '_created_at', '_updated_at', '_created_by_name', '_updated_by_name']
+      return view.schema.fields
+        .filter(f => systemFieldNames.includes(f.name))
+        .map(f => f.name)
+    }
+    return []
+  })
+
   // Local state for field name editing to avoid re-renders
   // Local state for field name editing to avoid re-renders
   const [editingFieldId, setEditingFieldId] = useState<string | null>(null)
   const [editingFieldId, setEditingFieldId] = useState<string | null>(null)
   const [editingFieldName, setEditingFieldName] = useState('')
   const [editingFieldName, setEditingFieldName] = useState('')
@@ -482,8 +503,8 @@ function ViewModal({
     setError(null)
     setError(null)
 
 
     try {
     try {
-      // Build schema in backend format
-      const schemaFields = sortedFields.map((f) => {
+      // Build schema in backend format - custom fields
+      const customFields = sortedFields.map((f) => {
         const field: Omit<SchemaField, '_id'> = {
         const field: Omit<SchemaField, '_id'> = {
           name: f.name,
           name: f.name,
           type: f.type,
           type: f.type,
@@ -506,6 +527,23 @@ function ViewModal({
         return field
         return field
       })
       })
 
 
+      // Add enabled system fields (at the end with high display_order)
+      const systemFieldsToInclude = enabledSystemFields.map((fieldName, index) => {
+        const systemField = SYSTEM_FIELDS.find(sf => sf.name === fieldName)
+        return {
+          name: fieldName,
+          type: systemField?.type || 'text',
+          label: systemField?.label || fieldName,
+          description: systemField?.description || '',
+          required: false,
+          display_order: 1000 + index, // Put system fields at the end
+          widget: '',
+          group: '_system',
+        }
+      })
+
+      const schemaFields = [...customFields, ...systemFieldsToInclude]
+
       const payload = {
       const payload = {
         name,
         name,
         collection_name: collectionName,
         collection_name: collectionName,
@@ -601,10 +639,47 @@ function ViewModal({
               </div>
               </div>
             </div>
             </div>
 
 
+            {/* System Fields */}
+            <div className="mt-6">
+              <h3 className="mb-3 text-lg font-medium text-gray-900">System Fields</h3>
+              <p className="mb-3 text-sm text-gray-500">
+                Include built-in document metadata fields in your view
+              </p>
+              <div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
+                {SYSTEM_FIELDS.map((sysField) => (
+                  <label
+                    key={sysField.name}
+                    className={`flex cursor-pointer items-start gap-3 rounded-lg border p-3 transition ${
+                      enabledSystemFields.includes(sysField.name)
+                        ? 'border-primary-500 bg-primary-50'
+                        : 'border-gray-200 hover:border-gray-300'
+                    }`}
+                  >
+                    <input
+                      type="checkbox"
+                      checked={enabledSystemFields.includes(sysField.name)}
+                      onChange={(e) => {
+                        if (e.target.checked) {
+                          setEnabledSystemFields([...enabledSystemFields, sysField.name])
+                        } else {
+                          setEnabledSystemFields(enabledSystemFields.filter(f => f !== sysField.name))
+                        }
+                      }}
+                      className="mt-0.5 h-4 w-4 rounded border-gray-300"
+                    />
+                    <div>
+                      <span className="font-medium text-gray-900">{sysField.label}</span>
+                      <p className="text-xs text-gray-500">{sysField.description}</p>
+                    </div>
+                  </label>
+                ))}
+              </div>
+            </div>
+
             {/* Schema Designer */}
             {/* Schema Designer */}
             <div className="mt-6">
             <div className="mt-6">
               <div className="mb-3 flex items-center justify-between">
               <div className="mb-3 flex items-center justify-between">
-                <h3 className="text-lg font-medium text-gray-900">Fields</h3>
+                <h3 className="text-lg font-medium text-gray-900">Custom Fields</h3>
                 <Button type="button" size="sm" onClick={addField}>
                 <Button type="button" size="sm" onClick={addField}>
                   <svg className="-ml-1 mr-1 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                   <svg className="-ml-1 mr-1 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                     <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
                     <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />