All files / src/layouts PlatformLayout.tsx

94.11% Statements 16/17
100% Branches 15/15
85.71% Functions 6/7
94.11% Lines 16/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110                                        2x 39x 39x 39x 39x 39x     39x   39x     39x   39x 2x     39x 1x     39x                 1x                         2x                                                                                        
import React, { useState, useRef } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import { Moon, Sun, Globe, Menu } from 'lucide-react';
import { User } from '../types';
import PlatformSidebar from '../components/PlatformSidebar';
import UserProfileDropdown from '../components/UserProfileDropdown';
import NotificationDropdown from '../components/NotificationDropdown';
import LanguageSelector from '../components/LanguageSelector';
import TicketModal from '../components/TicketModal';
import FloatingHelpButton from '../components/FloatingHelpButton';
import { useTicket } from '../hooks/useTickets';
import { useScrollToTop } from '../hooks/useScrollToTop';
 
interface PlatformLayoutProps {
  user: User;
  darkMode: boolean;
  toggleTheme: () => void;
  onSignOut: () => void;
}
 
const PlatformLayout: React.FC<PlatformLayoutProps> = ({ user, darkMode, toggleTheme, onSignOut }) => {
  const [isCollapsed, setIsCollapsed] = useState(false);
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
  const [ticketModalId, setTicketModalId] = useState<string | null>(null);
  const mainContentRef = useRef<HTMLElement>(null);
  const location = useLocation();
 
  // Pages that need edge-to-edge rendering (no padding)
  const noPaddingRoutes = ['/help/api-docs'];
 
  useScrollToTop(mainContentRef);
 
  // Fetch ticket data when modal is opened from notification
  const { data: ticketFromNotification } = useTicket(ticketModalId && ticketModalId !== 'undefined' ? ticketModalId : undefined);
 
  const handleTicketClick = (ticketId: string) => {
    setTicketModalId(ticketId);
  };
 
  const closeTicketModal = () => {
    setTicketModalId(null);
  };
 
  return (
    <div className="flex h-screen bg-gray-100 dark:bg-gray-900">
      {/* Floating Help Button */}
      <FloatingHelpButton />
 
      {/* Mobile menu */}
      <div className={`fixed inset-y-0 left-0 z-40 transform ${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'} transition-transform duration-300 ease-in-out md:hidden`}>
        <PlatformSidebar user={user} isCollapsed={false} toggleCollapse={() => { }} />
      </div>
      {isMobileMenuOpen && <div className="fixed inset-0 z-30 bg-black/50 md:hidden" onClick={() => setIsMobileMenuOpen(false)}></div>}
 
      {/* Static sidebar for desktop */}
      <div className="hidden md:flex md:flex-shrink-0">
        <PlatformSidebar user={user} isCollapsed={isCollapsed} toggleCollapse={() => setIsCollapsed(!isCollapsed)} />
      </div>
 
      {/* Main Content Area */}
      <div className="flex flex-col flex-1 min-w-0 overflow-hidden">
        {/* Platform Top Bar */}
        <header className="flex items-center justify-between h-16 px-4 sm:px-8 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
          <div className="flex items-center gap-4">
            <button
              onClick={() => setIsMobileMenuOpen(true)}
              className="p-2 -ml-2 text-gray-500 rounded-md md:hidden hover:bg-gray-100 dark:hover:bg-gray-700"
              aria-label="Open sidebar"
            >
              <Menu size={24} />
            </button>
            <div className="hidden md:flex items-center text-gray-500 dark:text-gray-400 text-sm gap-2">
              <Globe size={16} />
              <span>smoothschedule.com</span>
              <span className="mx-2 text-gray-300">/</span>
              <span className="text-gray-900 dark:text-white font-medium">Admin Console</span>
            </div>
          </div>
 
          <div className="flex items-center gap-4">
            <LanguageSelector />
            <button
              onClick={toggleTheme}
              className="p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
            >
              {darkMode ? <Sun size={20} /> : <Moon size={20} />}
            </button>
            <NotificationDropdown onTicketClick={handleTicketClick} />
            <UserProfileDropdown user={user} />
          </div>
        </header>
 
        <main ref={mainContentRef} className={`flex-1 overflow-auto bg-gray-50 dark:bg-gray-900 ${noPaddingRoutes.includes(location.pathname) ? '' : 'p-8'}`}>
          <Outlet />
        </main>
      </div>
 
      {/* Ticket modal opened from notification */}
      {ticketModalId && ticketFromNotification && (
        <TicketModal
          ticket={ticketFromNotification}
          onClose={closeTicketModal}
        />
      )}
    </div>
  );
};
 
export default PlatformLayout;