diff --git a/backend/migrations/040_fixes.sql b/backend/migrations/040_fixes.sql new file mode 100644 index 0000000..64ba10b --- /dev/null +++ b/backend/migrations/040_fixes.sql @@ -0,0 +1,20 @@ +-- Fix 1: unique general chat room per hotel (prevent duplicates on every open) +-- First deduplicate: keep the oldest general room per hotel, delete the rest +DELETE FROM chat_rooms +WHERE type = 'general' + AND id NOT IN ( + SELECT DISTINCT ON (hotel_id) id + FROM chat_rooms + WHERE type = 'general' + ORDER BY hotel_id, created_at ASC + ); + +-- Now add the unique constraint +CREATE UNIQUE INDEX IF NOT EXISTS uq_chat_rooms_general_per_hotel + ON chat_rooms (hotel_id) + WHERE type = 'general'; + +-- Fix 2: normalize loyalty_tier values that don't match our 4 tiers +UPDATE guests +SET loyalty_tier = 'bronze' +WHERE loyalty_tier NOT IN ('bronze', 'silver', 'gold', 'platinum'); diff --git a/backend/src/routes/chat.ts b/backend/src/routes/chat.ts index 2fc0d3f..4f4ed6c 100644 --- a/backend/src/routes/chat.ts +++ b/backend/src/routes/chat.ts @@ -18,7 +18,7 @@ const chatRoutes: FastifyPluginAsync = async (fastify) => { const { rows } = await db.query( `INSERT INTO chat_rooms (hotel_id, type, name) VALUES ($1, 'general', 'Общий чат') - ON CONFLICT DO NOTHING + ON CONFLICT (hotel_id) WHERE type = 'general' DO NOTHING RETURNING id`, [hotelId], ) diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 3c8fc28..c9b750d 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -33,16 +33,16 @@ type GroupKey = 'basics' | 'prices' | 'service' | 'management' | 'settingsGroup' const DEFAULT_GROUPS: Record = { basics: true, - prices: true, - service: true, - management: true, - settingsGroup: true, - devGroup: true, + prices: false, + service: false, + management: false, + settingsGroup: false, + devGroup: false, } function loadGroups(): Record { try { - const stored = JSON.parse(localStorage.getItem('sidebarGroups') || '{}') + const stored = JSON.parse(localStorage.getItem('sidebarGroups_v2') || '{}') return { ...DEFAULT_GROUPS, ...stored } } catch { return { ...DEFAULT_GROUPS } @@ -50,7 +50,7 @@ function loadGroups(): Record { } function saveGroups(groups: Record) { - localStorage.setItem('sidebarGroups', JSON.stringify(groups)) + localStorage.setItem('sidebarGroups_v2', JSON.stringify(groups)) } // ── NavItem with star ────────────────────────────────────────────────────── diff --git a/src/pages/LoyaltyPage.tsx b/src/pages/LoyaltyPage.tsx index 4d439ed..35ff7a6 100644 --- a/src/pages/LoyaltyPage.tsx +++ b/src/pages/LoyaltyPage.tsx @@ -214,8 +214,9 @@ const MANUAL_REASONS = [ 'Другое', ] -function levelBadge(levelId: LevelId) { - const level = DEFAULT_LEVELS.find(l => l.id === levelId)! +function levelBadge(levelId: string) { + const level = DEFAULT_LEVELS.find(l => l.id === levelId) ?? DEFAULT_LEVELS[0] + const safeId = (DEFAULT_LEVELS.find(l => l.id === levelId)?.id ?? 'bronze') as LevelId const bgs: Record = { bronze: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400', silver: 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-300', @@ -223,7 +224,7 @@ function levelBadge(levelId: LevelId) { platinum: 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400', } return ( - + {level.name}