151 lines
4.4 KiB
TypeScript
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>
|
|
)
|
|
}
|