Fix mobile UI bugs from screenshots

- Notifications panel: fixed positioning on mobile (full-width below topbar,
  no more overflow behind calendar)
- BookingModal: two-column layout stacks vertically on mobile (flex-col sm:flex-row)
- TariffsPage cards: price + actions merged into right column, no text overlap
- DiscountsPage cards: same fix, price text reduced to text-lg
- ReviewsPage: action buttons moved inside content div (no longer overlap guest name)
- RoomCategoriesPage: edit/delete buttons stack as column, 'Номера' text hidden on mobile
- PosPage: show desktop-only message on mobile instead of broken layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 00:47:24 +03:00
parent eeadbf0f5a
commit 5429b272f9
7 changed files with 78 additions and 63 deletions

View File

@@ -223,7 +223,7 @@ export function BookingModal({ open, draft, rooms, onClose, onSave, existing }:
{/* ── BOOKING TAB ── */}
{activeTab === 'booking' && (
<div className="space-y-4">
<div className="flex gap-6">
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
{/* ── LEFT COLUMN ── */}
<div className="flex-1 space-y-3 min-w-0">
{/* Room */}

View File

@@ -58,7 +58,7 @@ function NotificationsPanel({ onClose }: { onClose: () => void }) {
: notifications
return (
<div className="absolute right-0 top-full mt-2 w-[calc(100vw-1rem)] sm:w-96 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl shadow-2xl z-20 flex flex-col" style={{ maxHeight: 'calc(100vh - 80px)' }}>
<div className="fixed left-2 right-2 top-16 sm:absolute sm:left-auto sm:right-0 sm:top-full sm:mt-2 sm:w-96 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl shadow-2xl z-50 flex flex-col" style={{ maxHeight: 'calc(100vh - 80px)' }}>
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-100 dark:border-slate-700 shrink-0">
<div className="flex items-center gap-2">

View File

@@ -376,18 +376,15 @@ export function DiscountsPage() {
</div>
</div>
{/* Value badge */}
<div className="text-right shrink-0">
{/* Value + Actions (right column) */}
<div className="shrink-0 flex flex-col items-end gap-2">
<p className={cn(
'text-xl font-bold',
'text-lg font-bold',
discount.value === 100 ? 'text-emerald-600 dark:text-emerald-400' : 'text-brand-600 dark:text-brand-400',
)}>
{discount.value === 100 ? 'Бесплатно' : `${discount.value}${discount.valueType === 'percent' ? '%' : ' ₽'}`}
</p>
</div>
{/* Actions */}
<div className="flex items-center gap-1 shrink-0">
<div className="flex items-center gap-1">
<button
onClick={() => toggleActive(discount.id)}
className={cn('p-1.5 rounded-lg transition-colors', discount.isActive ? 'text-brand-600' : 'text-slate-400')}
@@ -402,6 +399,7 @@ export function DiscountsPage() {
className="p-1.5 rounded-lg text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors">
<Trash2 size={15} />
</button>
</div>
</div>
</div>
))}

View File

@@ -202,6 +202,22 @@ export function PosPage() {
return (
<div className="flex h-full flex-col">
{/* ── Desktop-only notice on mobile ── */}
<div className="sm:hidden flex-1 flex flex-col items-center justify-center gap-4 p-8 text-center">
<div className="w-16 h-16 rounded-2xl bg-slate-100 dark:bg-slate-700 flex items-center justify-center">
<Receipt size={28} className="text-slate-400" />
</div>
<div>
<p className="text-lg font-semibold text-slate-700 dark:text-slate-300">Касса только десктоп</p>
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1">
Откройте приложение на компьютере или планшете для работы с кассой
</p>
</div>
</div>
{/* ── Full POS UI (desktop only) ── */}
<div className="hidden sm:flex sm:flex-col sm:flex-1 sm:overflow-hidden">
{/* ── Top bar ── */}
<div className="shrink-0 px-4 py-3 border-b border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800">
<div className="flex items-center justify-between flex-wrap gap-2">
@@ -875,6 +891,7 @@ export function PosPage() {
</div>
</div>
)}
</div>{/* end desktop-only wrapper */}
</div>
)
}

View File

@@ -735,7 +735,7 @@ export function ReviewsPage() {
review.status === 'pending' && 'border-red-300 dark:border-red-700/60 bg-red-50/50 dark:bg-red-900/10',
)}
>
<div className="flex items-start gap-3 flex-wrap">
<div className="flex items-start gap-3">
<div className={cn('w-10 h-10 rounded-full flex items-center justify-center gap-0.5 text-sm font-bold shrink-0', ratingColor(review.rating, threshold))}>
{review.rating}<Star size={10} className="fill-current" />
</div>
@@ -808,7 +808,7 @@ export function ReviewsPage() {
</div>
{/* Actions */}
<div className="flex gap-2 shrink-0 flex-wrap">
<div className="flex gap-2 flex-wrap mt-3">
{review.status === 'pending' && !review.reply && replyId !== review.id && (
<>
<button

View File

@@ -356,27 +356,27 @@ export function RoomCategoriesPage() {
</div>
{/* Actions */}
<div className="flex items-center gap-2 shrink-0">
{cat.photos.length > 0 && (
<span className="text-xs text-slate-400">{cat.photos.length} фото</span>
)}
<div className="flex flex-col items-end gap-2 shrink-0">
<div className="flex items-center gap-1.5">
<button
onClick={() => openEdit(cat)}
className="p-1.5 rounded-lg border border-slate-200 dark:border-slate-600 hover:border-brand-400 text-slate-500 hover:text-brand-600 transition-colors"
>
<Pencil size={14} />
</button>
<button
onClick={() => handleDelete(cat.id)}
className="p-1.5 rounded-lg border border-slate-200 dark:border-slate-600 hover:border-red-400 text-slate-500 hover:text-red-500 transition-colors"
>
<Trash2 size={14} />
</button>
</div>
<button
onClick={() => openEdit(cat)}
className="p-2 rounded-lg border border-slate-200 dark:border-slate-600 hover:border-brand-400 text-slate-500 hover:text-brand-600 transition-colors"
className="flex items-center gap-1 px-2.5 py-1.5 rounded-lg bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 text-xs font-medium hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors whitespace-nowrap"
>
<Pencil size={14} />
</button>
<button
onClick={() => handleDelete(cat.id)}
className="p-2 rounded-lg border border-slate-200 dark:border-slate-600 hover:border-red-400 text-slate-500 hover:text-red-500 transition-colors"
>
<Trash2 size={14} />
</button>
<button
onClick={() => openEdit(cat)}
className="flex items-center gap-1 px-3 py-1.5 rounded-lg bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 text-xs font-medium hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors"
>
Номера <ChevronRight size={12} />
<span className="hidden sm:inline">Номера</span>
<ChevronRight size={12} />
</button>
</div>
</div>

View File

@@ -508,40 +508,40 @@ export function TariffsPage() {
</div>
</div>
{/* Price modifier */}
<div className="text-right shrink-0">
<p className={cn(
'text-sm font-semibold',
tariff.modifierValue > 0 ? 'text-amber-600 dark:text-amber-400'
: tariff.modifierValue < 0 ? 'text-emerald-600 dark:text-emerald-400'
: 'text-slate-500',
)}>
{modLabel}
</p>
<p className="text-xs text-slate-400">к базовой цене</p>
</div>
{/* Actions */}
<div className="flex items-center gap-1 shrink-0">
<button
onClick={() => toggleActive(tariff.id)}
className={cn('p-1.5 rounded-lg transition-colors', tariff.isActive ? 'text-brand-600 hover:bg-brand-50' : 'text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700')}
title={tariff.isActive ? 'Деактивировать' : 'Активировать'}
>
{tariff.isActive ? <ToggleRight size={20} /> : <ToggleLeft size={20} />}
</button>
<button
onClick={() => setModal(tariff)}
className="p-1.5 rounded-lg text-slate-400 hover:text-brand-600 hover:bg-brand-50 dark:hover:bg-brand-900/20 transition-colors"
>
<Edit2 size={15} />
</button>
<button
onClick={() => setDeleteTarget(tariff)}
className="p-1.5 rounded-lg text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
>
<Trash2 size={15} />
</button>
{/* Price + Actions (right column) */}
<div className="shrink-0 flex flex-col items-end gap-2">
<div className="text-right">
<p className={cn(
'text-sm font-semibold',
tariff.modifierValue > 0 ? 'text-amber-600 dark:text-amber-400'
: tariff.modifierValue < 0 ? 'text-emerald-600 dark:text-emerald-400'
: 'text-slate-500',
)}>
{modLabel}
</p>
<p className="text-xs text-slate-400">к базе</p>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => toggleActive(tariff.id)}
className={cn('p-1.5 rounded-lg transition-colors', tariff.isActive ? 'text-brand-600 hover:bg-brand-50' : 'text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700')}
title={tariff.isActive ? 'Деактивировать' : 'Активировать'}
>
{tariff.isActive ? <ToggleRight size={20} /> : <ToggleLeft size={20} />}
</button>
<button
onClick={() => setModal(tariff)}
className="p-1.5 rounded-lg text-slate-400 hover:text-brand-600 hover:bg-brand-50 dark:hover:bg-brand-900/20 transition-colors"
>
<Edit2 size={15} />
</button>
<button
onClick={() => setDeleteTarget(tariff)}
className="p-1.5 rounded-lg text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
>
<Trash2 size={15} />
</button>
</div>
</div>
</div>
)