Enhance EntryTraceDialog component by refactoring data display logic into dedicated summary functions for improved readability and maintainability. Update GridTab to streamline checkbox state management with a new toggle function. Refactor RulesCard to simplify window removal logic. Adjust CollectionsPanel, GenresPanel, GroupsPanel, RolesPanel, ShowsPanel, and UsersPanel to import sorting utilities from a centralized location, enhancing code organization. Update ThemeProvider to utilize a shared theme context for better consistency across the application.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
export type SortState = { key: string; desc: boolean }
|
||||
|
||||
/**
|
||||
* Состояние сортировки таблицы. Клик по новому столбцу — по возрастанию; повторный клик по тому же —
|
||||
* переключает направление. Для серверных списков `sort`/`desc` передаются в API (и в queryKey), для
|
||||
* клиентских — в {@link sortRows}.
|
||||
*/
|
||||
export function useTableSort(defaultKey: string, defaultDesc = false) {
|
||||
const [sort, setSort] = useState<SortState>({ key: defaultKey, desc: defaultDesc })
|
||||
const toggle = (key: string) =>
|
||||
setSort((s) => (s.key === key ? { key, desc: !s.desc } : { key, desc: false }))
|
||||
return { sort, toggle }
|
||||
}
|
||||
|
||||
type Comparable = string | number | boolean | null | undefined
|
||||
|
||||
/** Клиентская сортировка строк по выбранному ключу (для непагинированных списков). nulls — в конец. */
|
||||
export function sortRows<T>(
|
||||
rows: T[],
|
||||
sort: SortState,
|
||||
accessors: Record<string, (row: T) => Comparable>,
|
||||
): T[] {
|
||||
const accessor = accessors[sort.key]
|
||||
if (!accessor) return rows
|
||||
const dir = sort.desc ? -1 : 1
|
||||
return [...rows].sort((a, b) => {
|
||||
const av = accessor(a)
|
||||
const bv = accessor(b)
|
||||
if (av == null && bv == null) return 0
|
||||
if (av == null) return 1
|
||||
if (bv == null) return -1
|
||||
if (typeof av === 'string' && typeof bv === 'string') return av.localeCompare(bv) * dir
|
||||
return (av < bv ? -1 : av > bv ? 1 : 0) * dir
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useCallback, useMemo, useState, type ReactNode } from 'react'
|
||||
import {
|
||||
ToastContext,
|
||||
registerToastPush,
|
||||
type ToastItem,
|
||||
type ToastVariant,
|
||||
} from './toast-store'
|
||||
|
||||
let nextId = 1
|
||||
|
||||
export function ToastProvider({ children }: { children: ReactNode }) {
|
||||
const [toasts, setToasts] = useState<ToastItem[]>([])
|
||||
|
||||
const push = useCallback((message: string, variant: ToastVariant) => {
|
||||
setToasts((prev) => [...prev, { id: nextId++, message, variant }])
|
||||
}, [])
|
||||
|
||||
const dismiss = useCallback((id: number) => {
|
||||
setToasts((prev) => prev.filter((t) => t.id !== id))
|
||||
}, [])
|
||||
|
||||
registerToastPush(push)
|
||||
|
||||
// Литерал в value пересоздавался бы на каждый рендер провайдера и перерисовывал всех потребителей
|
||||
// контекста, даже когда список тостов не менялся.
|
||||
const value = useMemo(() => ({ toasts, dismiss }), [toasts, dismiss])
|
||||
|
||||
return <ToastContext value={value}>{children}</ToastContext>
|
||||
}
|
||||
@@ -1,20 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { ArrowDown, ArrowUp, ChevronsUpDown } from 'lucide-react'
|
||||
import { cn } from '@/shared/lib/cn'
|
||||
|
||||
type SortState = { key: string; desc: boolean }
|
||||
|
||||
/**
|
||||
* Состояние сортировки таблицы. Клик по новому столбцу — по возрастанию; повторный клик по тому же —
|
||||
* переключает направление. Для серверных списков `sort`/`desc` передаются в API (и в queryKey), для
|
||||
* клиентских — в {@link sortRows}.
|
||||
*/
|
||||
export function useTableSort(defaultKey: string, defaultDesc = false) {
|
||||
const [sort, setSort] = useState<SortState>({ key: defaultKey, desc: defaultDesc })
|
||||
const toggle = (key: string) =>
|
||||
setSort((s) => (s.key === key ? { key, desc: !s.desc } : { key, desc: false }))
|
||||
return { sort, toggle }
|
||||
}
|
||||
import type { SortState } from '@/shared/lib/table-sort'
|
||||
|
||||
/** Заголовок-кнопка столбца со стрелкой сортировки. */
|
||||
export function SortHeader({
|
||||
@@ -53,25 +39,3 @@ export function SortHeader({
|
||||
</th>
|
||||
)
|
||||
}
|
||||
|
||||
type Comparable = string | number | boolean | null | undefined
|
||||
|
||||
/** Клиентская сортировка строк по выбранному ключу (для непагинированных списков). nulls — в конец. */
|
||||
export function sortRows<T>(
|
||||
rows: T[],
|
||||
sort: SortState,
|
||||
accessors: Record<string, (row: T) => Comparable>,
|
||||
): T[] {
|
||||
const accessor = accessors[sort.key]
|
||||
if (!accessor) return rows
|
||||
const dir = sort.desc ? -1 : 1
|
||||
return [...rows].sort((a, b) => {
|
||||
const av = accessor(a)
|
||||
const bv = accessor(b)
|
||||
if (av == null && bv == null) return 0
|
||||
if (av == null) return 1
|
||||
if (bv == null) return -1
|
||||
if (typeof av === 'string' && typeof bv === 'string') return av.localeCompare(bv) * dir
|
||||
return (av < bv ? -1 : av > bv ? 1 : 0) * dir
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { createContext, useContext } from 'react'
|
||||
|
||||
export type ToastVariant = 'default' | 'success' | 'error'
|
||||
export type ToastItem = { id: number; message: string; variant: ToastVariant }
|
||||
|
||||
export type ToastContextValue = {
|
||||
toasts: ToastItem[]
|
||||
dismiss: (id: number) => void
|
||||
}
|
||||
|
||||
export const ToastContext = createContext<ToastContextValue | null>(null)
|
||||
|
||||
let pushImpl: ((message: string, variant: ToastVariant) => void) | null = null
|
||||
|
||||
/** Провайдер отдаёт сюда свою реализацию — через неё работает императивный {@link toast}. */
|
||||
export function registerToastPush(push: (message: string, variant: ToastVariant) => void) {
|
||||
pushImpl = push
|
||||
}
|
||||
|
||||
export function useToastContext() {
|
||||
const ctx = useContext(ToastContext)
|
||||
if (!ctx) throw new Error('useToastContext must be used within ToastProvider')
|
||||
return ctx
|
||||
}
|
||||
|
||||
/** Императивный вызов из любого места (не только компонентов). */
|
||||
export const toast = {
|
||||
success: (message: string) => pushImpl?.(message, 'success'),
|
||||
error: (message: string) => pushImpl?.(message, 'error'),
|
||||
message: (message: string) => pushImpl?.(message, 'default'),
|
||||
}
|
||||
Reference in New Issue
Block a user