Enhance user and media management with sorting and statistics: add sorting options to user and media listing endpoints, implement media statistics retrieval, and update frontend components for sorting and displaying media processing times. Refactor related query handlers and API types to support new features.
This commit is contained in:
@@ -1,16 +1,19 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { Trash2, Upload } from 'lucide-react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { HttpError } from '@/shared/api/client'
|
||||
import type { ImageCategory } from '@/shared/api/types'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/shared/ui/dialog'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
|
||||
import { toast } from '@/shared/ui/toast-store'
|
||||
import { deleteImage, imageUrl, listImages, uploadImage } from './api'
|
||||
|
||||
const CATEGORIES: ImageCategory[] = ['Library', 'ShowPoster', 'EpisodeStill', 'BumperBackground']
|
||||
|
||||
type ImageOrder = 'new' | 'old' | 'az' | 'za'
|
||||
|
||||
export type ImagePick = { id: string; url: string }
|
||||
|
||||
/**
|
||||
@@ -35,11 +38,30 @@ export function GalleryBrowser({
|
||||
const onError = (error: unknown) =>
|
||||
toast.error(error instanceof HttpError ? error.detail : t('common.error'))
|
||||
|
||||
const [order, setOrder] = useState<ImageOrder>('new')
|
||||
|
||||
const { data: images, isLoading } = useQuery({
|
||||
queryKey: ['admin', 'images', active],
|
||||
queryFn: () => listImages(active),
|
||||
})
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
const arr = [...(images ?? [])]
|
||||
arr.sort((a, b) => {
|
||||
switch (order) {
|
||||
case 'old':
|
||||
return a.createdAt.localeCompare(b.createdAt)
|
||||
case 'az':
|
||||
return (a.originalFileName ?? '').localeCompare(b.originalFileName ?? '')
|
||||
case 'za':
|
||||
return (b.originalFileName ?? '').localeCompare(a.originalFileName ?? '')
|
||||
default:
|
||||
return b.createdAt.localeCompare(a.createdAt)
|
||||
}
|
||||
})
|
||||
return arr
|
||||
}, [images, order])
|
||||
|
||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: ['admin', 'images', active] })
|
||||
|
||||
const pick = (id: string) => {
|
||||
@@ -87,6 +109,17 @@ export function GalleryBrowser({
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{onSelect ? t('admin.gallery.pickHint') : t('admin.gallery.browseHint')}
|
||||
</span>
|
||||
<Select value={order} onValueChange={(v) => setOrder(v as ImageOrder)}>
|
||||
<SelectTrigger className="ml-auto h-8 w-40">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="new">{t('admin.gallery.sort.newest')}</SelectItem>
|
||||
<SelectItem value="old">{t('admin.gallery.sort.oldest')}</SelectItem>
|
||||
<SelectItem value="az">{t('admin.gallery.sort.nameAsc')}</SelectItem>
|
||||
<SelectItem value="za">{t('admin.gallery.sort.nameDesc')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<input
|
||||
ref={fileInput}
|
||||
type="file"
|
||||
@@ -114,7 +147,7 @@ export function GalleryBrowser({
|
||||
<p className="py-6 text-center text-sm text-muted-foreground">{t('common.loading')}</p>
|
||||
) : images && images.length > 0 ? (
|
||||
<div className="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-5">
|
||||
{images.map((img) => (
|
||||
{sorted.map((img) => (
|
||||
<div key={img.id} className="group relative">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user