فهرست منبع

feat: format dates as YYYY-MM-DD HH:mm:ss with created/updated by info

- Add formatDateTime utility function
- Update all pages to use consistent date format
- Add Updated column to all list views
- Show "by {username}" when created_by_name/updated_by_name available
- Update types to include created_by/updated_by fields
Fszontagh 6 ماه پیش
والد
کامیت
4bab0a4029

+ 11 - 8
webui/src/pages/ApiKeys.tsx

@@ -5,6 +5,7 @@ import apiClient from '@/api/client'
 import { useAuth } from '@/contexts/AuthContext'
 import Button from '@/components/Button'
 import Input from '@/components/Input'
+import { formatDateTime } from '@/utils/format'
 
 interface ApiKey {
   id: string
@@ -395,6 +396,9 @@ function ApiKeys() {
                   <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
                     Permissions
                   </th>
+                  <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
+                    Created
+                  </th>
                   <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
                     Last Used
                   </th>
@@ -429,15 +433,14 @@ function ApiKeys() {
                         ))}
                       </div>
                     </td>
-                    <td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
-                      {apiKey.last_used_at
-                        ? new Date(apiKey.last_used_at).toLocaleDateString()
-                        : 'Never'}
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      {formatDateTime(apiKey.created_at)}
+                    </td>
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      {apiKey.last_used_at ? formatDateTime(apiKey.last_used_at) : 'Never'}
                     </td>
-                    <td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
-                      {apiKey.expires_at
-                        ? new Date(apiKey.expires_at).toLocaleDateString()
-                        : 'Never'}
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      {apiKey.expires_at ? formatDateTime(apiKey.expires_at) : 'Never'}
                     </td>
                     <td className="whitespace-nowrap px-6 py-4 text-right">
                       <button

+ 15 - 2
webui/src/pages/Collections.tsx

@@ -9,6 +9,7 @@ import Button from '@/components/Button'
 import Input from '@/components/Input'
 import FieldPermissionEditor from '@/components/FieldPermissionEditor'
 import type { Collection, Group, FieldPermission } from '@/types'
+import { formatDateTime } from '@/utils/format'
 
 interface CollectionFormData {
   name: string
@@ -467,6 +468,9 @@ function Collections() {
                   <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
                     Created
                   </th>
+                  <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
+                    Updated
+                  </th>
                   <th className="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
                     Actions
                   </th>
@@ -516,8 +520,17 @@ function Collections() {
                         <span className="text-gray-400">-</span>
                       )}
                     </td>
-                    <td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
-                      {new Date(collection.created_at).toLocaleDateString()}
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      <div>{formatDateTime(collection.created_at)}</div>
+                      {collection.created_by_name && (
+                        <div className="text-xs text-gray-400">by {collection.created_by_name}</div>
+                      )}
+                    </td>
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      <div>{formatDateTime(collection.updated_at)}</div>
+                      {collection.updated_by_name && (
+                        <div className="text-xs text-gray-400">by {collection.updated_by_name}</div>
+                      )}
                     </td>
                     <td className="whitespace-nowrap px-6 py-4 text-right">
                       <div className="flex justify-end gap-2">

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

@@ -7,6 +7,7 @@ import apiClient from '@/api/client'
 import { useWorkspace } from '@/contexts/WorkspaceContext'
 import Button from '@/components/Button'
 import Input from '@/components/Input'
+import { formatDateTime } from '@/utils/format'
 
 interface SchemaProperty {
   type: string
@@ -45,6 +46,10 @@ interface Document {
   _id: string
   _created_at: string
   _updated_at: string
+  _created_by?: string
+  _updated_by?: string
+  _created_by_name?: string
+  _updated_by_name?: string
   [key: string]: unknown
 }
 
@@ -515,8 +520,10 @@ function ViewDocumentModal({
     // Add metadata fields last
     allFields.push(
       { key: '_id', title: 'ID', value: document._id },
-      { key: '_created_at', title: 'Created At', value: document._created_at },
-      { key: '_updated_at', title: 'Updated At', value: document._updated_at }
+      { key: '_created_at', title: 'Created At', value: formatDateTime(document._created_at) },
+      { key: '_created_by', title: 'Created By', value: document._created_by_name || document._created_by || '-' },
+      { key: '_updated_at', title: 'Updated At', value: formatDateTime(document._updated_at) },
+      { key: '_updated_by', title: 'Updated By', value: document._updated_by_name || document._updated_by || '-' }
     )
 
     return allFields
@@ -868,6 +875,29 @@ function Documents() {
                           )}
                         </div>
                       </th>
+                      <th
+                        onClick={() => handleSort('_updated_at')}
+                        className="cursor-pointer px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 hover:bg-gray-100"
+                      >
+                        <div className="flex items-center gap-1">
+                          Updated
+                          {sortField === '_updated_at' && (
+                            <svg
+                              className={`h-4 w-4 transition ${sortOrder === 'desc' ? 'rotate-180' : ''}`}
+                              fill="none"
+                              viewBox="0 0 24 24"
+                              stroke="currentColor"
+                            >
+                              <path
+                                strokeLinecap="round"
+                                strokeLinejoin="round"
+                                strokeWidth={2}
+                                d="M5 15l7-7 7 7"
+                              />
+                            </svg>
+                          )}
+                        </div>
+                      </th>
                       <th className="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
                         Actions
                       </th>
@@ -890,8 +920,17 @@ function Documents() {
                             {doc._id.slice(0, 8)}...
                           </td>
                         )}
-                        <td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
-                          {new Date(doc._created_at).toLocaleDateString()}
+                        <td className="px-6 py-4 text-sm text-gray-500">
+                          <div>{formatDateTime(doc._created_at)}</div>
+                          {doc._created_by_name && (
+                            <div className="text-xs text-gray-400">by {doc._created_by_name}</div>
+                          )}
+                        </td>
+                        <td className="px-6 py-4 text-sm text-gray-500">
+                          <div>{formatDateTime(doc._updated_at)}</div>
+                          {doc._updated_by_name && (
+                            <div className="text-xs text-gray-400">by {doc._updated_by_name}</div>
+                          )}
                         </td>
                         <td className="whitespace-nowrap px-6 py-4 text-right">
                           <div className="flex justify-end gap-2">

+ 15 - 2
webui/src/pages/Groups.tsx

@@ -7,6 +7,7 @@ import Button from '@/components/Button'
 import Input from '@/components/Input'
 import PermissionEditor from '@/components/PermissionEditor'
 import type { Group } from '@/types'
+import { formatDateTime } from '@/utils/format'
 
 interface GroupFormData {
   name: string
@@ -338,6 +339,9 @@ function Groups() {
                   <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
                     Created
                   </th>
+                  <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
+                    Updated
+                  </th>
                   <th className="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
                     Actions
                   </th>
@@ -393,8 +397,17 @@ function Groups() {
                         <span className="text-gray-400">-</span>
                       )}
                     </td>
-                    <td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
-                      {new Date(group.created_at).toLocaleDateString()}
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      <div>{formatDateTime(group.created_at)}</div>
+                      {group.created_by_name && (
+                        <div className="text-xs text-gray-400">by {group.created_by_name}</div>
+                      )}
+                    </td>
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      <div>{formatDateTime(group.updated_at)}</div>
+                      {group.updated_by_name && (
+                        <div className="text-xs text-gray-400">by {group.updated_by_name}</div>
+                      )}
                     </td>
                     <td className="whitespace-nowrap px-6 py-4 text-right">
                       {group.is_system ? (

+ 15 - 2
webui/src/pages/Users.tsx

@@ -6,6 +6,7 @@ import { useWorkspace } from '@/contexts/WorkspaceContext'
 import Button from '@/components/Button'
 import Input from '@/components/Input'
 import type { User } from '@/types'
+import { formatDateTime } from '@/utils/format'
 
 interface UserFormData {
   email: string
@@ -306,6 +307,9 @@ function Users() {
                   <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
                     Created
                   </th>
+                  <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
+                    Updated
+                  </th>
                   <th className="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
                     Actions
                   </th>
@@ -330,8 +334,17 @@ function Users() {
                         {user.id.slice(0, 8)}...
                       </code>
                     </td>
-                    <td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
-                      {new Date(user.created_at).toLocaleDateString()}
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      <div>{formatDateTime(user.created_at)}</div>
+                      {user.created_by_name && (
+                        <div className="text-xs text-gray-400">by {user.created_by_name}</div>
+                      )}
+                    </td>
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      <div>{formatDateTime(user.updated_at)}</div>
+                      {user.updated_by_name && (
+                        <div className="text-xs text-gray-400">by {user.updated_by_name}</div>
+                      )}
                     </td>
                     <td className="whitespace-nowrap px-6 py-4 text-right">
                       <div className="flex justify-end gap-2">

+ 15 - 2
webui/src/pages/Workspaces.tsx

@@ -6,6 +6,7 @@ import { useWorkspace } from '@/contexts/WorkspaceContext'
 import Button from '@/components/Button'
 import Input from '@/components/Input'
 import type { Workspace } from '@/types'
+import { formatDateTime } from '@/utils/format'
 
 interface WorkspaceFormData {
   name: string
@@ -260,6 +261,9 @@ function Workspaces() {
                   <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
                     Created
                   </th>
+                  <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
+                    Updated
+                  </th>
                   <th className="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
                     Actions
                   </th>
@@ -276,8 +280,17 @@ function Workspaces() {
                         {workspace.id.slice(0, 8)}...
                       </code>
                     </td>
-                    <td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
-                      {new Date(workspace.created_at).toLocaleDateString()}
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      <div>{formatDateTime(workspace.created_at)}</div>
+                      {workspace.created_by_name && (
+                        <div className="text-xs text-gray-400">by {workspace.created_by_name}</div>
+                      )}
+                    </td>
+                    <td className="px-6 py-4 text-sm text-gray-500">
+                      <div>{formatDateTime(workspace.updated_at)}</div>
+                      {workspace.updated_by_name && (
+                        <div className="text-xs text-gray-400">by {workspace.updated_by_name}</div>
+                      )}
                     </td>
                     <td className="whitespace-nowrap px-6 py-4 text-right">
                       <div className="flex justify-end gap-2">

+ 20 - 0
webui/src/types/index.ts

@@ -7,6 +7,10 @@ export interface User {
   is_superadmin?: boolean
   created_at: string
   updated_at: string
+  created_by?: string
+  updated_by?: string
+  created_by_name?: string
+  updated_by_name?: string
 }
 
 export interface Workspace {
@@ -15,6 +19,10 @@ export interface Workspace {
   settings: Record<string, unknown>
   created_at: string
   updated_at: string
+  created_by?: string
+  updated_by?: string
+  created_by_name?: string
+  updated_by_name?: string
 }
 
 export interface AuthTokens {
@@ -40,6 +48,10 @@ export interface Group {
   created_at: string
   updated_at: string
   deleted_at?: string
+  created_by?: string
+  updated_by?: string
+  created_by_name?: string
+  updated_by_name?: string
 }
 
 export interface CreateGroupRequest {
@@ -79,6 +91,10 @@ export interface Collection {
   is_system: boolean
   created_at: string
   updated_at: string
+  created_by?: string
+  updated_by?: string
+  created_by_name?: string
+  updated_by_name?: string
   settings?: CollectionSettings
 }
 
@@ -187,4 +203,8 @@ export interface Membership {
   role?: string
   created_at: string
   updated_at: string
+  created_by?: string
+  updated_by?: string
+  created_by_name?: string
+  updated_by_name?: string
 }

+ 44 - 0
webui/src/utils/format.ts

@@ -0,0 +1,44 @@
+// Formatting utilities for SmartBotic CRM WebUI
+
+/**
+ * Format a date string to "YYYY-MM-DD HH:mm:ss" format
+ */
+export function formatDateTime(dateString: string | undefined | null): string {
+  if (!dateString) return '-'
+
+  try {
+    const date = new Date(dateString)
+    if (isNaN(date.getTime())) return '-'
+
+    const year = date.getFullYear()
+    const month = String(date.getMonth() + 1).padStart(2, '0')
+    const day = String(date.getDate()).padStart(2, '0')
+    const hours = String(date.getHours()).padStart(2, '0')
+    const minutes = String(date.getMinutes()).padStart(2, '0')
+    const seconds = String(date.getSeconds()).padStart(2, '0')
+
+    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+  } catch {
+    return '-'
+  }
+}
+
+/**
+ * Format a date string to "YYYY-MM-DD" format (date only)
+ */
+export function formatDate(dateString: string | undefined | null): string {
+  if (!dateString) return '-'
+
+  try {
+    const date = new Date(dateString)
+    if (isNaN(date.getTime())) return '-'
+
+    const year = date.getFullYear()
+    const month = String(date.getMonth() + 1).padStart(2, '0')
+    const day = String(date.getDate()).padStart(2, '0')
+
+    return `${year}-${month}-${day}`
+  } catch {
+    return '-'
+  }
+}