diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 0c22eae..bc2c360 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -44,9 +44,9 @@ export function Sidebar({ open, onClose }: SidebarProps) { const isModuleActive = (id: string) => statuses[id] === 'active' || statuses[id] === 'trial' // Core modules with dedicated nav positions (not shown in generic module list) - const CORE_MODULE_IDS = ['housekeeping', 'channel-manager'] + const CORE_MODULE_IDS = ['channel-manager'] - // Extra module items for the "Модули" sidebar section (excluding core ones) + // Module items for the "Модули" sidebar section const activeModuleItems = MODULES_DATA.filter( m => m.sidebarItem && !m.sidebarItem.showForHousekeeper && @@ -112,14 +112,26 @@ export function Sidebar({ open, onClose }: SidebarProps) { ) : ( <> + {/* ── Основное ── */}

Основное

{!isHousekeeper && ( - + <> + + + + + + )} + + {/* ── Модули ── */} + {(isModuleActive('housekeeping') || (!isHousekeeper && activeModuleItems.length > 0)) && ( +

+ Модули +

)} - {/* Housekeeping — controlled by module status, visible to all roles */} {isModuleActive('housekeeping') && ( )} + {!isHousekeeper && activeModuleItems.map(m => ( + + ))} + {/* ── Управление ── */} {!isHousekeeper && ( <>

Управление

- - - - + {isModuleActive('channel-manager') && ( - {/* Active addon modules */} - {activeModuleItems.length > 0 && ( - <> -

- Модули -

- {activeModuleItems.map(m => ( - - ))} - - )} -

Разработчикам

diff --git a/src/contexts/ModulesContext.tsx b/src/contexts/ModulesContext.tsx index 0879932..cf35a16 100644 --- a/src/contexts/ModulesContext.tsx +++ b/src/contexts/ModulesContext.tsx @@ -16,6 +16,7 @@ const DEFAULT_STATUSES: Record = { 'room-service': 'active', 'rental': 'active', 'migration': 'active', + 'documents': 'active', } interface ModulesContextValue { @@ -26,7 +27,7 @@ interface ModulesContextValue { const ModulesContext = createContext(null) -const MODULES_VERSION = '2' +const MODULES_VERSION = '3' export function ModulesProvider({ children }: { children: React.ReactNode }) { const [statuses, setStatuses] = useState>(() => { diff --git a/src/pages/HousekeepingPage.tsx b/src/pages/HousekeepingPage.tsx index 4e295d1..dbf4ea5 100644 --- a/src/pages/HousekeepingPage.tsx +++ b/src/pages/HousekeepingPage.tsx @@ -1,10 +1,21 @@ import { useState } from 'react' -import { CheckCircle2, Clock, AlertCircle, Sparkles, User } from 'lucide-react' +import { CheckCircle2, Clock, Sparkles, User, ListChecks, Settings2, Save } from 'lucide-react' import { MOCK_HK_TASKS } from '../data/mockData' import type { HousekeepingTask } from '../types' import { cn } from '../lib/utils' import { Badge } from '../components/ui/Badge' +function Toggle({ on, onChange }: { on: boolean; onChange: () => void }) { + return ( + + ) +} + type Column = 'pending' | 'in_progress' | 'done' const COLUMNS: { id: Column; label: string; icon: React.ElementType; color: string }[] = [ @@ -35,6 +46,36 @@ const TYPE_LABELS = { export function HousekeepingPage() { const [tasks, setTasks] = useState(MOCK_HK_TASKS) + const [activeTab, setActiveTab] = useState<'tasks' | 'plans'>('tasks') + const [planSaved, setPlanSaved] = useState(false) + + // Cleaning plan settings + const [plans, setPlans] = useState({ + checkoutAuto: true, + checkoutPriority: 'high' as 'high' | 'normal', + checkoutInspection: true, + + dailyEnabled: true, + dailyIntervalDays: 1, + dailyStartFromDay: 1, + + onDemandEnabled: true, + onDemandPriority: 'normal' as 'high' | 'normal' | 'low', + + deepCleanEnabled: false, + deepCleanEveryDays: 7, + + inspectionAfterClean: true, + autoAssign: false, + }) + + const setP = (k: K, v: typeof plans[K]) => + setPlans(prev => ({ ...prev, [k]: v })) + + const savePlans = () => { + setPlanSaved(true) + setTimeout(() => setPlanSaved(false), 2000) + } const updateStatus = (id: string, status: HousekeepingTask['status']) => { setTasks(prev => prev.map(t => @@ -50,58 +91,272 @@ export function HousekeepingPage() { return (
{/* Header */} -
-

Уборка и обслуживание

-
-
-
-
- - {done} / {total} завершено - +
+
+

Уборка и обслуживание

+ {activeTab === 'tasks' && ( +
+
+
+
+ + {done} / {total} завершено + +
+ )}
- {/* Kanban columns */} -
- {COLUMNS.map(col => { - const colTasks = tasks.filter(t => t.status === col.id) - const Icon = col.icon - return ( -
- {/* Column header */} -
- -

- {col.label} -

- - {colTasks.length} - -
- - {/* Cards */} -
- {colTasks.map(task => ( - - ))} - {colTasks.length === 0 && ( -
-

Пусто

-
- )} -
-
- ) - })} + {/* Tabs */} +
+ {([ + { id: 'tasks' as const, label: 'Задачи на сегодня', icon: ListChecks }, + { id: 'plans' as const, label: 'Планы уборки', icon: Settings2 }, + ]).map(t => ( + + ))}
+ + {/* ── Tasks tab ── */} + {activeTab === 'tasks' && ( +
+ {COLUMNS.map(col => { + const colTasks = tasks.filter(t => t.status === col.id) + const Icon = col.icon + return ( +
+
+ +

{col.label}

+ + {colTasks.length} + +
+
+ {colTasks.map(task => ( + + ))} + {colTasks.length === 0 && ( +
+

Пусто

+
+ )} +
+
+ ) + })} +
+ )} + + {/* ── Plans tab ── */} + {activeTab === 'plans' && ( +
+ + {/* Checkout cleaning */} +
+
+
+

Уборка при выезде

+

+ Автоматически создавать задачу на уборку, как только гость освобождает номер +

+
+ setP('checkoutAuto', !plans.checkoutAuto)} /> +
+ {plans.checkoutAuto && ( +
+
+ +
+ {([['high', 'Срочно'], ['normal', 'Обычный']] as const).map(([v, l]) => ( + + ))} +
+
+
+
+

Инспекция после уборки

+

Создавать задачу проверки для старшей горничной

+
+ setP('checkoutInspection', !plans.checkoutInspection)} /> +
+
+ )} +
+ + {/* Daily cleaning */} +
+
+
+

Ежедневная уборка

+

+ Регулярная уборка в номерах с проживающими гостями +

+
+ setP('dailyEnabled', !plans.dailyEnabled)} /> +
+ {plans.dailyEnabled && ( +
+
+
+ +
+ {[1, 2, 3, 7].map(n => ( + + ))} +
+
+
+ + setP('dailyStartFromDay', Math.max(1, parseInt(e.target.value) || 1))} + /> +

+ Уборка начнётся на {plans.dailyStartFromDay}-й день проживания +

+
+
+
+ )} +
+ + {/* On-demand cleaning */} +
+
+
+

Уборка по запросу гостя

+

+ Гость может запросить уборку через QR-код или мобильное приложение +

+
+ setP('onDemandEnabled', !plans.onDemandEnabled)} /> +
+ {plans.onDemandEnabled && ( +
+ +
+ {([['high', 'Срочно'], ['normal', 'Обычный'], ['low', 'Низкий']] as const).map(([v, l]) => ( + + ))} +
+
+ )} +
+ + {/* Deep cleaning */} +
+
+
+

Генеральная уборка

+

+ Глубокая уборка с чисткой мебели, мытьём окон и полной сменой постельного белья +

+
+ setP('deepCleanEnabled', !plans.deepCleanEnabled)} /> +
+ {plans.deepCleanEnabled && ( +
+ + setP('deepCleanEveryDays', Math.max(1, parseInt(e.target.value) || 7))} + /> + дней +
+ )} +
+ + {/* Inspection after cleaning */} +
+
+
+

Проверка после уборки

+

+ После завершения любой уборки создавать задачу инспекции для старшей горничной +

+
+ setP('inspectionAfterClean', !plans.inspectionAfterClean)} /> +
+
+ + {/* Auto assign */} +
+
+
+

Автоназначение горничной

+

+ Автоматически назначать ответственную горничную по расписанию смен (без этой опции — задачи назначаются вручную) +

+
+ setP('autoAssign', !plans.autoAssign)} /> +
+
+ +
+ +
+
+ )}
) }