300 lines
10 KiB
TypeScript
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;
|