Files
TeleWave/frontend/src/features/admin/groups/GroupsPanel.tsx
T

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>
)
}