Add Prettier to the frontend and gate formatting in CI

Форматтера у фронтенда не было: стиль держался вручную и успел разъехаться в 50 файлах.
Ставим Prettier с настройками под уже сложившийся стиль (без точек с запятой, одинарные кавычки,
ширина 100 — подобрана замером: при 100 расходится меньше файлов, чем при 96 или 110) и
прогоняем его по коду. `src/routeTree.gen.ts` исключён — его переписывает плагин роутера.

Чтобы форматирование больше не расходилось незаметно, добавлены проверки в CI: `csharpier check`
для бэкенда (его отсутствие и позволило накопиться 79 неотформатированным файлам) и
`prettier --check` для фронтенда. Версии форматтеров прибиты точно, без кареток: минорка меняет
вывод и красит CI на файлах, которых никто не трогал.

`.editorconfig` задаёт редакторам те же отступы и LF ещё до форматтера; значения совпадают с
настройками csharpier и Prettier намеренно — оба его читают.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-07-27 01:38:12 +03:00
co-authored by Claude Opus 5
parent 0442056367
commit 0606ea3e6e
58 changed files with 5158 additions and 4903 deletions
@@ -6,13 +6,7 @@ import { useApiError } from '@/shared/lib/use-api-error'
import { Badge } from '@/shared/ui/badge'
import { Button } from '@/shared/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/shared/ui/card'
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/shared/ui/dialog'
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/shared/ui/dialog'
import { Input } from '@/shared/ui/input'
import { Label } from '@/shared/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
@@ -224,7 +218,9 @@ export function UsersPanel() {
<td className="px-4 py-2">
<Select
value={roles?.find((r) => r.name === user.role)?.id}
onValueChange={(newRoleId) => changeRoleMutation.mutate({ userId: user.id, roleId: newRoleId })}
onValueChange={(newRoleId) =>
changeRoleMutation.mutate({ userId: user.id, roleId: newRoleId })
}
>
<SelectTrigger className="h-8 w-32">
<SelectValue>{user.role}</SelectValue>
@@ -251,18 +247,30 @@ export function UsersPanel() {
<td className="px-4 py-2">
<div className="flex gap-2">
{user.isBlocked ? (
<Button size="sm" variant="outline" onClick={() => unblockMutation.mutate(user.id)}>
<Button
size="sm"
variant="outline"
onClick={() => unblockMutation.mutate(user.id)}
>
{t('admin.users.unblock')}
</Button>
) : (
<Button size="sm" variant="outline" onClick={() => blockMutation.mutate(user.id)}>
<Button
size="sm"
variant="outline"
onClick={() => blockMutation.mutate(user.id)}
>
{t('admin.users.block')}
</Button>
)}
<Button size="sm" variant="outline" onClick={() => setResetTarget(user)}>
{t('admin.users.resetPassword')}
</Button>
<Button size="sm" variant="destructive" onClick={() => deleteMutation.mutate(user.id)}>
<Button
size="sm"
variant="destructive"
onClick={() => deleteMutation.mutate(user.id)}
>
{t('common.delete')}
</Button>
</div>
@@ -275,13 +283,23 @@ export function UsersPanel() {
{totalPages > 1 && (
<div className="flex items-center justify-center gap-2 text-sm">
<Button size="sm" variant="outline" disabled={page <= 1} onClick={() => setPage((p) => p - 1)}>
<Button
size="sm"
variant="outline"
disabled={page <= 1}
onClick={() => setPage((p) => p - 1)}
>
</Button>
<span>
{page} / {totalPages}
</span>
<Button size="sm" variant="outline" disabled={page >= totalPages} onClick={() => setPage((p) => p + 1)}>
<Button
size="sm"
variant="outline"
disabled={page >= totalPages}
onClick={() => setPage((p) => p + 1)}
>
</Button>
</div>