Files
smoothschedule/frontend/src/components/Sidebar.tsx

300 lines
10 KiB
TypeScript

import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useLocation } from 'react-router-dom';
import {
LayoutDashboard,
CalendarDays,
Settings,
Users,
CreditCard,
MessageSquare,
LogOut,
ClipboardList,
Briefcase,
Ticket,
HelpCircle,
Clock,
Plug,
FileSignature,
CalendarOff,
} from 'lucide-react';
import { Business, User } from '../types';
import { useLogout } from '../hooks/useAuth';
import { usePlanFeatures } from '../hooks/usePlanFeatures';
import SmoothScheduleLogo from './SmoothScheduleLogo';
import UnfinishedBadge from './ui/UnfinishedBadge';
import {
SidebarSection,
SidebarItem,
SidebarDivider,
} from './navigation/SidebarComponents';
interface SidebarProps {
business: Business;
user: User;
isCollapsed: boolean;
toggleCollapse: () => void;
}
const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCollapse }) => {
const { t } = useTranslation();
const { role } = user;
const logoutMutation = useLogout();
const { canUse } = usePlanFeatures();
const canViewAdminPages = role === 'owner' || role === 'manager';
const canViewManagementPages = role === 'owner' || role === 'manager';
const isStaff = role === 'staff';
const canViewSettings = role === 'owner';
const canViewTickets = role === 'owner' || role === 'manager' || (role === 'staff' && user.can_access_tickets);
const canSendMessages = user.can_send_messages === true;
const handleSignOut = () => {
logoutMutation.mutate();
};
return (
<div
className={`flex flex-col h-full text-white shrink-0 transition-all duration-300 ${isCollapsed ? 'w-20' : 'w-64'}`}
style={{
background: `linear-gradient(to bottom right, var(--color-brand-600, ${business.primaryColor}), var(--color-brand-secondary, ${business.secondaryColor || business.primaryColor}))`
}}
>
{/* Header / Logo */}
<button
onClick={toggleCollapse}
className={`flex items-center gap-3 w-full text-left px-6 py-6 ${isCollapsed ? 'justify-center' : ''} hover:bg-white/5 transition-colors focus:outline-none`}
aria-label={isCollapsed ? t('nav.expandSidebar') : t('nav.collapseSidebar')}
>
{business.logoDisplayMode === 'logo-only' && business.logoUrl ? (
<div className="flex items-center justify-center w-full">
<img
src={business.logoUrl}
alt={business.name}
className="max-w-full max-h-12 object-contain"
/>
</div>
) : (
<>
{business.logoUrl && business.logoDisplayMode !== 'text-only' ? (
<div className="flex items-center justify-center w-10 h-10 shrink-0">
<img
src={business.logoUrl}
alt={business.name}
className="w-full h-full object-contain"
/>
</div>
) : business.logoDisplayMode !== 'logo-only' && (
<div
className="flex items-center justify-center w-10 h-10 bg-white rounded-lg font-bold text-xl shrink-0"
style={{ color: 'var(--color-brand-600)' }}
>
{business.name.substring(0, 2).toUpperCase()}
</div>
)}
{!isCollapsed && business.logoDisplayMode !== 'logo-only' && (
<div className="overflow-hidden">
<h1 className="font-bold leading-tight truncate">{business.name}</h1>
<p className="text-xs text-white/60 truncate">{business.subdomain}.smoothschedule.com</p>
</div>
)}
</>
)}
</button>
{/* Navigation */}
<nav className="flex-1 px-3 space-y-6 overflow-y-auto pb-4">
{/* Core Features - Always visible */}
<SidebarSection isCollapsed={isCollapsed}>
<SidebarItem
to="/"
icon={LayoutDashboard}
label={t('nav.dashboard')}
isCollapsed={isCollapsed}
exact
/>
{!isStaff && (
<SidebarItem
to="/scheduler"
icon={CalendarDays}
label={t('nav.scheduler')}
isCollapsed={isCollapsed}
/>
)}
{!isStaff && (
<SidebarItem
to="/tasks"
icon={Clock}
label={t('nav.tasks', 'Tasks')}
isCollapsed={isCollapsed}
locked={!canUse('plugins') || !canUse('tasks')}
badgeElement={<UnfinishedBadge />}
/>
)}
{isStaff && (
<SidebarItem
to="/my-schedule"
icon={CalendarDays}
label={t('nav.mySchedule', 'My Schedule')}
isCollapsed={isCollapsed}
/>
)}
{(role === 'staff' || role === 'resource') && (
<SidebarItem
to="/my-availability"
icon={CalendarOff}
label={t('nav.myAvailability', 'My Availability')}
isCollapsed={isCollapsed}
/>
)}
</SidebarSection>
{/* Manage Section - Staff+ */}
{canViewManagementPages && (
<SidebarSection title={t('nav.sections.manage', 'Manage')} isCollapsed={isCollapsed}>
<SidebarItem
to="/customers"
icon={Users}
label={t('nav.customers')}
isCollapsed={isCollapsed}
badgeElement={<UnfinishedBadge />}
/>
<SidebarItem
to="/services"
icon={Briefcase}
label={t('nav.services', 'Services')}
isCollapsed={isCollapsed}
/>
<SidebarItem
to="/resources"
icon={ClipboardList}
label={t('nav.resources')}
isCollapsed={isCollapsed}
/>
{canViewAdminPages && (
<>
<SidebarItem
to="/staff"
icon={Users}
label={t('nav.staff')}
isCollapsed={isCollapsed}
badgeElement={<UnfinishedBadge />}
/>
{canUse('contracts') && (
<SidebarItem
to="/contracts"
icon={FileSignature}
label={t('nav.contracts', 'Contracts')}
isCollapsed={isCollapsed}
badgeElement={<UnfinishedBadge />}
/>
)}
<SidebarItem
to="/time-blocks"
icon={CalendarOff}
label={t('nav.timeBlocks', 'Time Blocks')}
isCollapsed={isCollapsed}
/>
</>
)}
</SidebarSection>
)}
{/* Communicate Section - Tickets + Messages */}
{(canViewTickets || canSendMessages) && (
<SidebarSection title={t('nav.sections.communicate', 'Communicate')} isCollapsed={isCollapsed}>
{canSendMessages && (
<SidebarItem
to="/messages"
icon={MessageSquare}
label={t('nav.messages')}
isCollapsed={isCollapsed}
/>
)}
{canViewTickets && (
<SidebarItem
to="/tickets"
icon={Ticket}
label={t('nav.tickets')}
isCollapsed={isCollapsed}
/>
)}
</SidebarSection>
)}
{/* Money Section - Payments */}
{canViewAdminPages && (
<SidebarSection title={t('nav.sections.money', 'Money')} isCollapsed={isCollapsed}>
<SidebarItem
to="/payments"
icon={CreditCard}
label={t('nav.payments')}
isCollapsed={isCollapsed}
disabled={!business.paymentsEnabled && role !== 'owner'}
/>
</SidebarSection>
)}
{/* Extend Section - Plugins */}
{canViewAdminPages && (
<SidebarSection title={t('nav.sections.extend', 'Extend')} isCollapsed={isCollapsed}>
<SidebarItem
to="/plugins/my-plugins"
icon={Plug}
label={t('nav.plugins', 'Plugins')}
isCollapsed={isCollapsed}
locked={!canUse('plugins')}
badgeElement={<UnfinishedBadge />}
/>
</SidebarSection>
)}
{/* Footer Section - Settings & Help */}
<SidebarDivider isCollapsed={isCollapsed} />
<SidebarSection isCollapsed={isCollapsed}>
{canViewSettings && (
<SidebarItem
to="/settings"
icon={Settings}
label={t('nav.businessSettings')}
isCollapsed={isCollapsed}
/>
)}
<SidebarItem
to="/help"
icon={HelpCircle}
label={t('nav.helpDocs', 'Help & Docs')}
isCollapsed={isCollapsed}
/>
</SidebarSection>
</nav>
{/* User Section */}
<div className="p-4 border-t border-white/10">
<a
href={`${window.location.protocol}//${window.location.host.split('.').slice(-2).join('.')}`}
target="_blank"
rel="noopener noreferrer"
className={`flex items-center gap-2 text-xs text-white/60 mb-3 hover:text-white/80 transition-colors ${isCollapsed ? 'justify-center' : ''}`}
>
<SmoothScheduleLogo className="w-5 h-5 text-white" />
{!isCollapsed && (
<span className="text-white/60">{t('nav.smoothSchedule')}</span>
)}
</a>
<button
onClick={handleSignOut}
disabled={logoutMutation.isPending}
className={`flex items-center gap-3 px-3 py-2 text-sm font-medium text-white/70 hover:text-white hover:bg-white/5 w-full transition-colors rounded-lg ${isCollapsed ? 'justify-center' : ''} disabled:opacity-50`}
>
<LogOut size={18} className="shrink-0" />
{!isCollapsed && <span>{t('auth.signOut')}</span>}
</button>
</div>
</div>
);
};
export default Sidebar;