feat(files): default sort by updated and add updated sort option (#4279)

* feat(files): default sort by updated and add updated sort option

* feat(files): show Last Updated column

Matches the visible-column pattern already used on Knowledge and Tables
so users can see the value they're sorting by.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-04-23 15:02:07 -07:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 3b11c814f8
commit 0ae19dab85
11 changed files with 15254 additions and 6 deletions
@@ -97,6 +97,7 @@ const COLUMNS: ResourceColumn[] = [
{ id: 'type', header: 'Type' },
{ id: 'created', header: 'Created' },
{ id: 'owner', header: 'Owner' },
{ id: 'updated', header: 'Last Updated' },
]
const MIME_TYPE_LABELS: Record<string, string> = {
@@ -249,7 +250,7 @@ export function Files() {
result = result.filter((f) => uploadedByFilter.includes(f.uploadedBy))
}
const col = activeSort?.column ?? 'created'
const col = activeSort?.column ?? 'updated'
const dir = activeSort?.direction ?? 'desc'
return [...result].sort((a, b) => {
let cmp = 0
@@ -266,6 +267,9 @@ export function Files() {
case 'created':
cmp = new Date(a.uploadedAt).getTime() - new Date(b.uploadedAt).getTime()
break
case 'updated':
cmp = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
break
case 'owner':
cmp = (members?.find((m) => m.userId === a.uploadedBy)?.name ?? '').localeCompare(
members?.find((m) => m.userId === b.uploadedBy)?.name ?? ''
@@ -310,6 +314,7 @@ export function Files() {
},
created: timeCell(file.uploadedAt),
owner: ownerCell(file.uploadedBy, members),
updated: timeCell(file.updatedAt),
},
}
nextCache.set(file.id, { row, file, members })
@@ -875,6 +880,7 @@ export function Files() {
{ id: 'size', label: 'Size' },
{ id: 'type', label: 'Type' },
{ id: 'created', label: 'Created' },
{ id: 'updated', label: 'Last Updated' },
{ id: 'owner', label: 'Owner' },
],
active: activeSort,
@@ -124,6 +124,7 @@ export const ResourceContent = memo(function ResourceContent({
type,
uploadedBy: '',
uploadedAt: STREAMING_EPOCH,
updatedAt: STREAMING_EPOCH,
}
}, [workspaceId, streamFileName])
@@ -216,7 +216,7 @@ export function Knowledge() {
result = result.filter((kb) => ownerFilter.includes(kb.userId))
}
const col = activeSort?.column ?? 'created'
const col = activeSort?.column ?? 'updated'
const dir = activeSort?.direction ?? 'desc'
return [...result].sort((a, b) => {
let cmp = 0
@@ -123,7 +123,7 @@ export function Tables() {
if (ownerFilter.length > 0) {
result = result.filter((t) => ownerFilter.includes(t.createdBy))
}
const col = activeSort?.column ?? 'created'
const col = activeSort?.column ?? 'updated'
const dir = activeSort?.direction ?? 'desc'
return [...result].sort((a, b) => {
let cmp = 0
@@ -29,6 +29,7 @@ function toFileRecord(row: typeof workspaceFiles.$inferSelect) {
uploadedBy: row.userId,
deletedAt: row.deletedAt,
uploadedAt: row.uploadedAt,
updatedAt: row.updatedAt,
storageContext: 'mothership' as const,
}
}
@@ -23,6 +23,7 @@ function toWorkspaceFileRecord(row: typeof workspaceFiles.$inferSelect): Workspa
uploadedBy: row.userId,
deletedAt: row.deletedAt,
uploadedAt: row.uploadedAt,
updatedAt: row.updatedAt,
storageContext: 'mothership',
}
}
@@ -45,6 +45,7 @@ export interface WorkspaceFileRecord {
uploadedBy: string
deletedAt?: Date | null
uploadedAt: Date
updatedAt: Date
/** Pass-through to `downloadFile` when not default `workspace` (e.g. chat mothership uploads). */
storageContext?: 'workspace' | 'mothership'
}
@@ -375,6 +376,7 @@ export async function getWorkspaceFileByName(
uploadedBy: file.userId,
deletedAt: file.deletedAt,
uploadedAt: file.uploadedAt,
updatedAt: file.updatedAt,
}
}
@@ -423,6 +425,7 @@ export async function listWorkspaceFiles(
uploadedBy: file.userId,
deletedAt: file.deletedAt,
uploadedAt: file.uploadedAt,
updatedAt: file.updatedAt,
}))
} catch (error) {
logger.error(`Failed to list workspace files for ${workspaceId}:`, error)
@@ -560,6 +563,7 @@ export async function getWorkspaceFile(
uploadedBy: file.userId,
deletedAt: file.deletedAt,
uploadedAt: file.uploadedAt,
updatedAt: file.updatedAt,
}
} catch (error) {
logger.error(`Failed to get workspace file ${fileId}:`, error)
@@ -638,7 +642,7 @@ export async function updateWorkspaceFileContent(
await db
.update(workspaceFiles)
.set({ size: content.length, contentType: nextContentType })
.set({ size: content.length, contentType: nextContentType, updatedAt: new Date() })
.where(
and(
eq(workspaceFiles.id, fileId),
@@ -707,7 +711,7 @@ export async function renameWorkspaceFile(
try {
updated = await db
.update(workspaceFiles)
.set({ originalName: trimmedName })
.set({ originalName: trimmedName, updatedAt: new Date() })
.where(
and(
eq(workspaceFiles.id, fileId),
@@ -807,7 +811,7 @@ export async function restoreWorkspaceFile(workspaceId: string, fileId: string):
await db
.update(workspaceFiles)
.set({ deletedAt: null, originalName: newName })
.set({ deletedAt: null, originalName: newName, updatedAt: new Date() })
.where(
and(
eq(workspaceFiles.id, fileId),
@@ -0,0 +1 @@
ALTER TABLE "workspace_files" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;
File diff suppressed because it is too large Load Diff
@@ -1373,6 +1373,13 @@
"when": 1776916912442,
"tag": "0196_retention_org_level",
"breakpoints": true
},
{
"idx": 197,
"version": "7",
"when": 1776980739421,
"tag": "0197_unknown_the_captain",
"breakpoints": true
}
]
}
+1
View File
@@ -1138,6 +1138,7 @@ export const workspaceFiles = pgTable(
size: integer('size').notNull(),
deletedAt: timestamp('deleted_at'),
uploadedAt: timestamp('uploaded_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
},
(table) => ({
keyActiveUniqueIdx: uniqueIndex('workspace_files_key_active_unique')