diff --git a/src/App.tsx b/src/App.tsx index f960651..bbc30a8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { ThemeProvider } from './contexts/ThemeContext' import { AuthProvider } from './contexts/AuthContext' import { ModulesProvider } from './contexts/ModulesContext' import { AmenitiesProvider } from './contexts/AmenitiesContext' +import { BedTypesProvider } from './contexts/BedTypesContext' import { NotificationsProvider } from './contexts/NotificationsContext' import { AppLayout } from './layouts/AppLayout' import { LoginPage } from './pages/LoginPage' @@ -45,6 +46,7 @@ export default function App() { + {/* Public */} @@ -89,6 +91,7 @@ export default function App() { } /> + diff --git a/src/components/rooms/RoomModal.tsx b/src/components/rooms/RoomModal.tsx index d8a6121..b531bb8 100644 --- a/src/components/rooms/RoomModal.tsx +++ b/src/components/rooms/RoomModal.tsx @@ -1,9 +1,10 @@ import { useState, useRef } from 'react' import { Modal } from '../ui/Modal' import { cn } from '../../lib/utils' -import type { Room, RoomStatus, HousekeepingStatus, BedType, BedItem, BedItemType, ExtraPlace, ChildPolicy } from '../../types' +import type { Room, RoomStatus, HousekeepingStatus, BedType, BedItem, ExtraPlace, ChildPolicy } from '../../types' import { ImagePlus, X as XIcon, ChevronLeft, ChevronRight, Plus, Minus, Baby, Trash2 } from 'lucide-react' import { useAmenities } from '../../contexts/AmenitiesContext' +import { useBedTypes } from '../../contexts/BedTypesContext' const ROOM_TYPES = ['Стандарт', 'Делюкс', 'Полулюкс', 'Люкс', 'Пентхаус', 'Апартаменты', 'Другой'] @@ -29,16 +30,6 @@ const HK_STATUSES: { value: HousekeepingStatus; label: string }[] = [ { value: 'inspect', label: 'Проверка' }, ] -const BED_ITEM_TYPES: { value: BedItemType; label: string; icon: string }[] = [ - { value: 'single', label: 'Кровать 1-сп.', icon: '🛏' }, - { value: 'double', label: 'Кровать 2-сп.', icon: '🛏' }, - { value: 'queen', label: 'Queen-кровать', icon: '🛏' }, - { value: 'king', label: 'King-кровать', icon: '🛏' }, - { value: 'twin', label: 'Две кровати', icon: '🛏' }, - { value: 'sofa', label: 'Диван', icon: '🛋' }, - { value: 'bunk', label: 'Двухъярусная', icon: '🛏' }, - { value: 'cot', label: 'Раскладушка', icon: '🪑' }, -] // ── Tab type ─────────────────────────────────────────────────────────────────── type ModalTab = 'main' | 'places' | 'description' | 'photos' @@ -78,6 +69,7 @@ export function RoomModal({ open, room, existingNumbers = [], categories = [], o }) const { amenities: allAmenities } = useAmenities() + const { bedTypes } = useBedTypes() const [amenities, setAmenities] = useState(room?.amenities ?? ['Wi-Fi', 'TV', 'AC']) const [photos, setPhotos] = useState(room?.photos ?? []) const [photoIdx, setPhotoIdx] = useState(0) @@ -423,14 +415,14 @@ export function RoomModal({ open, room, existingNumbers = [], categories = [], o {beds.map((bed, i) => (
- {BED_ITEM_TYPES.find(b => b.value === bed.type)?.icon ?? '🛏'} + {bedTypes.find(b => b.value === bed.type)?.icon ?? '🛏'} diff --git a/src/contexts/BedTypesContext.tsx b/src/contexts/BedTypesContext.tsx new file mode 100644 index 0000000..566de3f --- /dev/null +++ b/src/contexts/BedTypesContext.tsx @@ -0,0 +1,53 @@ +import { createContext, useContext, useState } from 'react' +import type { ReactNode } from 'react' + +export interface BedTypeItem { + value: string // unique key stored in DB (e.g. 'double', 'sofa') + label: string // display name + icon: string // emoji +} + +const DEFAULT_BED_TYPES: BedTypeItem[] = [ + { value: 'single', label: 'Кровать 1-сп.', icon: '🛏' }, + { value: 'double', label: 'Кровать 2-сп.', icon: '🛏' }, + { value: 'queen', label: 'Queen-кровать', icon: '🛏' }, + { value: 'king', label: 'King-кровать', icon: '🛏' }, + { value: 'twin', label: 'Две кровати', icon: '🛏' }, + { value: 'sofa', label: 'Диван', icon: '🛋' }, + { value: 'bunk', label: 'Двухъярусная', icon: '🛏' }, + { value: 'cot', label: 'Раскладушка', icon: '🪑' }, +] + +interface BedTypesContextValue { + bedTypes: BedTypeItem[] + addBedType: (item: BedTypeItem) => void + removeBedType: (value: string) => void + updateBedType: (value: string, patch: Partial>) => void +} + +const BedTypesContext = createContext(null) + +export function BedTypesProvider({ children }: { children: ReactNode }) { + const [bedTypes, setBedTypes] = useState(DEFAULT_BED_TYPES) + + const addBedType = (item: BedTypeItem) => + setBedTypes(prev => prev.find(b => b.value === item.value) ? prev : [...prev, item]) + + const removeBedType = (value: string) => + setBedTypes(prev => prev.filter(b => b.value !== value)) + + const updateBedType = (value: string, patch: Partial>) => + setBedTypes(prev => prev.map(b => b.value === value ? { ...b, ...patch } : b)) + + return ( + + {children} + + ) +} + +export function useBedTypes() { + const ctx = useContext(BedTypesContext) + if (!ctx) throw new Error('useBedTypes must be used within BedTypesProvider') + return ctx +} diff --git a/src/pages/RoomCategoriesPage.tsx b/src/pages/RoomCategoriesPage.tsx index da6cca5..5dc16e0 100644 --- a/src/pages/RoomCategoriesPage.tsx +++ b/src/pages/RoomCategoriesPage.tsx @@ -3,6 +3,7 @@ import { Plus, Pencil, Trash2, ImagePlus, X as XIcon, Tag, ChevronRight, Chevron import { cn } from '../lib/utils' import type { RoomCategory } from '../types' import { useAmenities } from '../contexts/AmenitiesContext' +import { useBedTypes } from '../contexts/BedTypesContext' import { useAuth } from '../contexts/AuthContext' import { api } from '../lib/api' import type { CategoryApi } from '../lib/api' @@ -362,6 +363,15 @@ export function RoomCategoriesPage() { const [editingAmenity, setEditingAmenity] = useState(null) const [editAmenityValue, setEditAmenityValue] = useState('') + // Bed types directory state + const { bedTypes, addBedType, removeBedType, updateBedType } = useBedTypes() + const [showBedTypes, setShowBedTypes] = useState(false) + const [newBedLabel, setNewBedLabel] = useState('') + const [newBedIcon, setNewBedIcon] = useState('🛏') + const [editingBed, setEditingBed] = useState(null) + const [editBedLabel, setEditBedLabel] = useState('') + const [editBedIcon, setEditBedIcon] = useState('') + useEffect(() => { if (!slug) return api.categories.list(slug) @@ -599,6 +609,139 @@ export function RoomCategoriesPage() { )}
+ {/* Справочник спальных мест */} +
+ + + {showBedTypes && ( +
+ {/* Add new */} +
+ + setNewBedLabel(e.target.value)} + onKeyDown={e => { + if (e.key === 'Enter') { + e.preventDefault() + const label = newBedLabel.trim() + if (!label) return + const value = label.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_а-яё]/gi, '') + + '_' + Date.now().toString(36) + addBedType({ value, label, icon: newBedIcon }) + setNewBedLabel('') + setNewBedIcon('🛏') + } + }} + /> + +
+ +
+ {bedTypes.map(b => ( +
+ {editingBed === b.value ? ( + <> + + setEditBedLabel(e.target.value)} + onKeyDown={e => { + if (e.key === 'Enter') { + updateBedType(b.value, { label: editBedLabel, icon: editBedIcon }) + setEditingBed(null) + } + if (e.key === 'Escape') setEditingBed(null) + }} + /> + + + + ) : ( + <> + {b.icon} + {b.label} +
+ + +
+ + )} +
+ ))} +
+
+ )} +
+ {formOpen && (