Files
TeleWave/frontend/src/features/admin/channels/ChannelsPanel.tsx
T

110 lines
3.7 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 { HttpError } from '@/shared/api/client'
import { Badge } from '@/shared/ui/badge'
import { Button } from '@/shared/ui/button'
import { Input } from '@/shared/ui/input'
import { toast } from '@/shared/ui/toast-store'
import { createChannel, listChannels } from './api'
function slugify(value: string) {
return value
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '')
}
export function ChannelsPanel() {
const { t } = useTranslation()
const queryClient = useQueryClient()
const [name, setName] = useState('')
const [slug, setSlug] = useState('')
const { data, isLoading } = useQuery({ queryKey: ['admin', 'channels'], queryFn: listChannels })
const invalidate = () => queryClient.invalidateQueries({ queryKey: ['admin', 'channels'] })
const onError = (error: unknown) =>
toast.error(error instanceof HttpError ? error.detail : t('common.error'))
const createMutation = useMutation({
mutationFn: () => createChannel({ name: name.trim(), slug: slug || slugify(name) }),
onSuccess: () => {
setName('')
setSlug('')
invalidate()
},
onError,
})
return (
<div className="flex flex-col gap-4">
<h2 className="crt-glow text-xl font-semibold">{t('admin.channels.title')}</h2>
<div className="flex flex-wrap items-end gap-2">
<Input
className="max-w-xs"
placeholder={t('admin.channels.name')}
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Input
className="max-w-xs"
placeholder={t('admin.channels.slug')}
value={slug}
onChange={(e) => setSlug(slugify(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>
<th className="px-4 py-2 font-medium">{t('admin.channels.name')}</th>
<th className="px-4 py-2 font-medium">{t('admin.channels.slug')}</th>
<th className="px-4 py-2 font-medium">{t('admin.channels.state')}</th>
</tr>
</thead>
<tbody>
{isLoading && (
<tr>
<td className="px-4 py-3 text-muted-foreground" colSpan={3}>
{t('common.loading')}
</td>
</tr>
)}
{data?.map((channel) => (
<tr key={channel.id} className="border-b border-border last:border-0">
<td className="px-4 py-2">
<Link
to="/admin/channels/$channelId"
params={{ channelId: channel.id }}
className="text-primary hover:underline"
>
{channel.name}
</Link>
</td>
<td className="px-4 py-2 text-muted-foreground">{channel.slug}</td>
<td className="px-4 py-2">
{channel.isEnabled ? (
<Badge>{t('admin.channels.enabled')}</Badge>
) : (
<Badge variant="muted">{t('admin.channels.disabled')}</Badge>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}