143 lines
4.9 KiB
TypeScript
143 lines
4.9 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|
import { Link } from '@tanstack/react-router'
|
|
import { useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { qk } from '@/shared/api/query-keys'
|
|
import { useApiError } from '@/shared/lib/use-api-error'
|
|
import { Badge } from '@/shared/ui/badge'
|
|
import { Button } from '@/shared/ui/button'
|
|
import { Input } from '@/shared/ui/input'
|
|
import { sortRows, useTableSort } from '@/shared/lib/table-sort'
|
|
import { SortHeader } from '@/shared/ui/sortable'
|
|
import { createGroup, deleteGroup, listGroups } from './api'
|
|
import { DurationLabel } from './DurationLabel'
|
|
|
|
export function GroupsPanel() {
|
|
const { t } = useTranslation()
|
|
const queryClient = useQueryClient()
|
|
const [name, setName] = useState('')
|
|
const { sort, toggle } = useTableSort('name', false)
|
|
|
|
const { data, isLoading } = useQuery({ queryKey: qk.groups.all, queryFn: listGroups })
|
|
|
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: qk.groups.all })
|
|
const onError = useApiError()
|
|
|
|
const createMutation = useMutation({
|
|
mutationFn: () => createGroup({ name: name.trim() }),
|
|
onSuccess: () => {
|
|
setName('')
|
|
invalidate()
|
|
},
|
|
onError,
|
|
})
|
|
const deleteMutation = useMutation({ mutationFn: deleteGroup, onSuccess: invalidate, onError })
|
|
|
|
const rows = sortRows(data ?? [], sort, {
|
|
name: (g) => g.name.toLowerCase(),
|
|
items: (g) => g.itemCount,
|
|
units: (g) => g.unitCount,
|
|
duration: (g) => g.totalDurationSeconds,
|
|
})
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<div className="flex flex-col gap-1">
|
|
<h2 className="crt-glow text-xl font-semibold">{t('admin.groups.title')}</h2>
|
|
<p className="text-sm text-muted-foreground">{t('admin.groups.hint')}</p>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<Input
|
|
className="max-w-xs"
|
|
placeholder={t('admin.groups.name')}
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
/>
|
|
<Button
|
|
size="sm"
|
|
disabled={!name.trim() || createMutation.isPending}
|
|
onClick={() => createMutation.mutate()}
|
|
>
|
|
{t('common.create')}
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="crt-panel overflow-x-auto rounded-md">
|
|
<table className="w-full text-sm">
|
|
<thead className="border-b border-border text-left text-muted-foreground">
|
|
<tr>
|
|
<SortHeader
|
|
label={t('admin.groups.name')}
|
|
sortKey="name"
|
|
sort={sort}
|
|
onToggle={toggle}
|
|
/>
|
|
<SortHeader
|
|
label={t('admin.groups.items')}
|
|
sortKey="items"
|
|
sort={sort}
|
|
onToggle={toggle}
|
|
/>
|
|
<SortHeader
|
|
label={t('admin.groups.units')}
|
|
sortKey="units"
|
|
sort={sort}
|
|
onToggle={toggle}
|
|
/>
|
|
<SortHeader
|
|
label={t('admin.groups.duration')}
|
|
sortKey="duration"
|
|
sort={sort}
|
|
onToggle={toggle}
|
|
/>
|
|
<th className="px-4 py-2 font-medium">{t('common.actions')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{isLoading && (
|
|
<tr>
|
|
<td className="px-4 py-3 text-muted-foreground" colSpan={5}>
|
|
{t('common.loading')}
|
|
</td>
|
|
</tr>
|
|
)}
|
|
{rows.map((group) => (
|
|
<tr key={group.id} className="border-b border-border last:border-0">
|
|
<td className="px-4 py-2">
|
|
<div className="flex items-center gap-2">
|
|
<Link
|
|
to="/admin/groups/$groupId"
|
|
params={{ groupId: group.id }}
|
|
className="text-primary hover:underline"
|
|
>
|
|
{group.name}
|
|
</Link>
|
|
{group.hasFilter && (
|
|
<Badge variant="muted">{t('admin.groups.hasFilter')}</Badge>
|
|
)}
|
|
</div>
|
|
</td>
|
|
<td className="px-4 py-2 text-muted-foreground">{group.itemCount}</td>
|
|
<td className="px-4 py-2 text-muted-foreground">{group.unitCount}</td>
|
|
<td className="px-4 py-2 text-muted-foreground">
|
|
<DurationLabel seconds={group.totalDurationSeconds} />
|
|
</td>
|
|
<td className="px-4 py-2">
|
|
<Button
|
|
size="sm"
|
|
variant="destructive"
|
|
onClick={() => deleteMutation.mutate(group.id)}
|
|
>
|
|
{t('common.delete')}
|
|
</Button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|