Paperjet/frontend/src/features/library/useLibrary.ts

206 lines
6.1 KiB
TypeScript

import { create } from 'zustand'
import { libraryApi } from './api'
import type { DocumentMeta, DocumentUpdateRequest } from './types'
interface LibraryState {
documents: DocumentMeta[]
total: number
isLoading: boolean
error: string | null
searchQuery: string
// Selection
selectedIds: Set<string>
toggleSelection: (id: string) => void
clearSelection: () => void
selectAll: () => void
// Actions
fetchDocuments: (query?: string) => Promise<void>
fetchTrash: () => Promise<void>
uploadDocument: (file: File) => Promise<DocumentMeta>
deleteDocument: (id: string, permanent?: boolean) => Promise<void>
restoreDocument: (id: string) => Promise<void>
bulkDelete: (permanent?: boolean) => Promise<void>
bulkRestore: () => Promise<void>
emptyTrash: () => Promise<void>
setSearchQuery: (query: string) => void
updateDocument: (id: string, data: DocumentUpdateRequest) => Promise<void>
}
function errorMessage(reason: unknown, fallback = 'Something went wrong.') {
if (reason instanceof Error && reason.message) return reason.message;
if (typeof reason === 'object' && reason !== null && 'error' in reason) {
const error = reason.error;
if (typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string') {
return error.message;
}
}
return fallback;
}
export const useLibrary = create<LibraryState>((set, get) => ({
documents: [],
total: 0,
isLoading: false,
error: null,
searchQuery: '',
selectedIds: new Set(),
toggleSelection: (id) => set((state) => {
const newSelection = new Set(state.selectedIds)
if (newSelection.has(id)) newSelection.delete(id)
else newSelection.add(id)
return { selectedIds: newSelection }
}),
clearSelection: () => set({ selectedIds: new Set() }),
selectAll: () => set((state) => ({
selectedIds: new Set(state.documents.map(d => d.id))
})),
setSearchQuery: (query) => set({ searchQuery: query }),
fetchDocuments: async (query = '') => {
set({ isLoading: true, error: null, searchQuery: query })
try {
const data = await libraryApi.getDocuments(query)
set({ documents: data.items, total: data.total, isLoading: false })
} catch (err: unknown) {
set({ error: errorMessage(err), isLoading: false })
}
},
fetchTrash: async () => {
set({ isLoading: true, error: null, searchQuery: '' })
try {
const data = await libraryApi.getTrash()
set({ documents: data.items, total: data.total, isLoading: false })
} catch (err: unknown) {
set({ error: errorMessage(err), isLoading: false })
}
},
uploadDocument: async (file: File) => {
set({ isLoading: true, error: null })
try {
const newDoc = await libraryApi.uploadDocument(file)
set((state) => ({
documents: [newDoc, ...state.documents],
total: state.total + 1,
isLoading: false
}))
return newDoc
} catch (err: unknown) {
set({ error: errorMessage(err), isLoading: false })
throw err
}
},
deleteDocument: async (id: string, permanent = false) => {
set({ isLoading: true, error: null })
try {
await libraryApi.deleteDocument(id, permanent)
set((state) => ({
documents: state.documents.filter(d => d.id !== id),
total: state.total - 1,
selectedIds: new Set(Array.from(state.selectedIds).filter(sid => sid !== id)),
isLoading: false
}))
} catch (err: unknown) {
set({ error: errorMessage(err), isLoading: false })
throw err
}
},
restoreDocument: async (id: string) => {
set({ isLoading: true, error: null })
try {
await libraryApi.restoreDocument(id)
set((state) => ({
documents: state.documents.filter(d => d.id !== id),
total: state.total - 1,
selectedIds: new Set(Array.from(state.selectedIds).filter(sid => sid !== id)),
isLoading: false
}))
} catch (err: unknown) {
set({ error: errorMessage(err), isLoading: false })
throw err
}
},
bulkDelete: async (permanent = false) => {
const { selectedIds } = get()
if (selectedIds.size === 0) return
set({ isLoading: true, error: null })
try {
if (permanent) {
// Must delete one by one if permanent
await Promise.all(Array.from(selectedIds).map(id => libraryApi.deleteDocument(id, true)))
} else {
await libraryApi.bulkDelete({ ids: Array.from(selectedIds) })
}
set((state) => ({
documents: state.documents.filter(d => !state.selectedIds.has(d.id)),
total: state.total - state.selectedIds.size,
selectedIds: new Set(),
isLoading: false
}))
} catch (err: unknown) {
set({ error: errorMessage(err), isLoading: false })
throw err
}
},
bulkRestore: async () => {
const { selectedIds } = get()
if (selectedIds.size === 0) return
set({ isLoading: true, error: null })
try {
await libraryApi.bulkRestore({ ids: Array.from(selectedIds) })
set((state) => ({
documents: state.documents.filter(d => !state.selectedIds.has(d.id)),
total: state.total - state.selectedIds.size,
selectedIds: new Set(),
isLoading: false
}))
} catch (err: unknown) {
set({ error: errorMessage(err), isLoading: false })
throw err
}
},
emptyTrash: async () => {
set({ isLoading: true, error: null })
try {
await libraryApi.emptyTrash()
set({
documents: [],
total: 0,
selectedIds: new Set(),
isLoading: false
})
} catch (err: unknown) {
set({ error: errorMessage(err), isLoading: false })
throw err
}
},
updateDocument: async (id: string, data: DocumentUpdateRequest) => {
set({ isLoading: true, error: null })
try {
const updatedDoc = await libraryApi.updateDocument(id, data)
set((state) => ({
documents: state.documents.map(d => d.id === id ? updatedDoc : d),
isLoading: false
}))
} catch (err: unknown) {
set({ error: errorMessage(err), isLoading: false })
throw err
}
}
}))