brf/client/admin/components/errors_page.tsx
2025-12-18 07:31:37 +01:00

151 lines
4.4 KiB
TypeScript

import { h } from 'preact'
import { useCallback, useEffect, useState } from 'preact/hooks'
import { route } from 'preact-router'
import rek, { type FetchError } from 'rek'
import { pick } from 'lowline'
import { useNotifications } from '../contexts/notifications.tsx'
import useItemsReducer from '../hooks/use_items_reducer.ts'
import useRequestState from '../../shared/hooks/use_request_state.ts'
import Button from './button.tsx'
import ErrorDetails from './error_details.tsx'
import ErrorsSearchForm from './errors_search_form.tsx'
import ErrorsTable from './errors_table.tsx'
import Modal from './modal.tsx'
import PageHeader from './page_header.tsx'
import Pagination from './pagination.tsx'
import Section from './section.tsx'
import s from './errors_page.module.scss'
const defaultSort = '-id'
export default function ErrorsPage() {
const { notify } = useNotifications()
const [{ pending }, request] = useRequestState()
const [pagination, setPagination] = useState({})
const [errors, items] = useItemsReducer()
const [selected, setSelected] = useState(null)
useEffect(() => {
fetch()
}, [location.search])
const fetch = useCallback(async () => {
try {
const { items: errors, ...pagination } = await rek(`/api/errors${location.search}`)
items.reset(errors)
setPagination(pagination)
} catch (err) {
notify.error((err as FetchError).body?.message || (err as FetchError).message)
}
}, [])
const onSearchSubmit = useCallback(
(query: ANY) => {
const currentParams = new URLSearchParams(location.search)
if (currentParams.has('sort')) query = { ...query, sort: currentParams.get('sort') }
route(location.pathname + (Object.keys(query).length ? '?' + new URLSearchParams(query) : ''))
},
[location.search],
)
const onSortBy = useCallback((column: string) => {
const searchParams = new URLSearchParams(location.search)
searchParams.delete('offset')
searchParams.set('sort', (searchParams.get('sort') || defaultSort) === column ? '-' + column : column)
route(location.pathname + '?' + searchParams)
}, [])
const onDelete = useCallback(async (id: number) => {
if (confirm(`Sure you want to remove error #${id}`)) {
try {
request.pending()
await rek.delete(`/api/errors/${id}`)
fetch()
notify.success(`Successfully deleted error #${id}`)
} catch {
notify.error(`Failed to delete error #${id}`)
}
request.reset()
}
}, [])
const deleteErrors = useCallback(
async (page: ANY) => {
if (confirm(page ? 'Delete errors on page?' : 'Delete all filtered errors?')) {
try {
request.pending()
await rek.delete('/api/errors', {
searchParams: page ? { id: errors.map((error) => error.id).join(',') } : location.search,
})
fetch()
notify.success(page ? 'Errors on page deleted' : 'All filtered errors deleted')
} catch (err) {
notify.error(`Error deleting errors: ${(err as FetchError).body?.message || (err as FetchError).message}`)
}
request.reset()
}
},
[errors],
)
return (
<section>
<PageHeader>Errors</PageHeader>
<Section>
<Section.Heading>List</Section.Heading>
<Section.Body className={s.top}>
<ErrorsSearchForm className={s.search} onSubmit={onSearchSubmit} />
<div className={s.delete}>
<Button color='red' className={s.button} onClick={() => deleteErrors(true)}>
Delete Page
</Button>
<Button color='red' className={s.button} onClick={() => deleteErrors(false)}>
Delete All
</Button>
</div>
</Section.Body>
<Section.Body noPadding>
<Pagination {...pagination} {...pick(location, 'search', 'pathname')} />
<ErrorsTable
defaultSort={defaultSort}
errors={errors}
onDelete={onDelete}
onSelect={setSelected}
pending={pending}
onSortBy={onSortBy}
/>
<Pagination {...pagination} {...pick(location, 'search', 'pathname')} />
</Section.Body>
</Section>
{selected != null && (
<Modal onClose={() => setSelected(null)}>
<ErrorDetails error={errors.find((error) => error.id === selected)} />
</Modal>
)}
</section>
)
}