fix: chat — VARCHAR(10) overflow for notifications type, skeleton loading

- Migration 072: widen type column to VARCHAR(20), add partial unique index for general rooms
- Backend: replace brittle ON CONFLICT with explicit SELECT-then-INSERT ensureRoom helper
- Frontend: show room skeletons (Общий чат + Уведомления) during load instead of spinner,
  hide '0 чатов' subtitle when list is empty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-14 12:03:35 +03:00
parent 3c4c40da44
commit d0dbf33701
3 changed files with 44 additions and 32 deletions

View File

@@ -322,7 +322,7 @@ export function ChatWidget() {
{view === 'search' && 'Поиск'}
{view === 'settings' && 'Настройки чата'}
</p>
{view === 'rooms' && (
{view === 'rooms' && visibleRooms.length > 0 && (
<p className="text-xs text-white/70">{visibleRooms.length} чатов</p>
)}
{view === 'messages' && activeRoom?.type === 'notifications' && (
@@ -348,10 +348,12 @@ export function ChatWidget() {
{/* ── Rooms list ── */}
{view === 'rooms' && (
<div className="flex-1 overflow-y-auto">
{loadingRooms ? (
<div className="flex items-center justify-center h-32">
<Loader2 size={20} className="animate-spin text-slate-400" />
</div>
{loadingRooms && visibleRooms.length === 0 ? (
// Skeleton placeholders while loading
<>
<RoomSkeleton icon={<Users size={16} className="text-brand-400" />} bg="bg-brand-100 dark:bg-brand-900/30" label="Общий чат" />
{settings.notifVisible && <RoomSkeleton icon={<Bell size={16} className="text-amber-500" />} bg="bg-amber-100 dark:bg-amber-900/30" label="Уведомления" />}
</>
) : visibleRooms.length === 0 ? (
<div className="text-center py-10 text-sm text-slate-400 px-4">
Нет чатов. Нажмите <PenSquare size={13} className="inline" /> чтобы начать.
@@ -681,3 +683,17 @@ function ToggleRow({ icon, label, checked, onChange }: {
</div>
)
}
function RoomSkeleton({ icon, bg, label }: { icon: React.ReactNode; bg: string; label: string }) {
return (
<div className="flex items-center gap-3 px-4 py-3 border-b border-slate-100 dark:border-slate-700/50 animate-pulse">
<div className={cn('w-9 h-9 rounded-full flex items-center justify-center shrink-0', bg)}>
{icon}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-slate-600 dark:text-slate-300">{label}</p>
<div className="h-3 w-24 bg-slate-200 dark:bg-slate-600 rounded mt-1" />
</div>
</div>
)
}