feat: Add event automation system for plugins attached to appointments

- Add GlobalEventPlugin model for auto-attaching plugins to all events
- Create signals for auto-attachment on new events and rescheduling
- Add API endpoints for global event plugins (CRUD, toggle, reapply)
- Update CreateTaskModal with "Scheduled Task" vs "Event Automation" choice
- Add option to apply to all events or future events only
- Display event automations in Tasks page alongside scheduled tasks
- Add EditEventAutomationModal for editing trigger and timing
- Handle event reschedule - update Celery task timing on time/duration changes
- Add Marketplace to Plugins menu in sidebar

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-11-29 12:17:28 -05:00
parent 9b106bf129
commit 0c7d76e264
18 changed files with 3061 additions and 175 deletions

View File

@@ -20,7 +20,9 @@ import {
LifeBuoy,
Zap,
Plug,
Package
Package,
Clock,
Store
} from 'lucide-react';
import { Business, User } from '../types';
import { useLogout } from '../hooks/useAuth';
@@ -39,7 +41,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
const { role } = user;
const logoutMutation = useLogout();
const [isHelpOpen, setIsHelpOpen] = useState(location.pathname.startsWith('/help') || location.pathname === '/support');
const [isPluginsOpen, setIsPluginsOpen] = useState(location.pathname.startsWith('/plugins'));
const [isPluginsOpen, setIsPluginsOpen] = useState(location.pathname.startsWith('/plugins') || location.pathname === '/plugins/marketplace');
const getNavClass = (path: string, exact: boolean = false, disabled: boolean = false) => {
const isActive = exact
@@ -134,6 +136,11 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
{!isCollapsed && <span>{t('nav.scheduler')}</span>}
</Link>
<Link to="/tasks" className={getNavClass('/tasks')} title={t('nav.tasks', 'Tasks')}>
<Clock size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.tasks', 'Tasks')}</span>}
</Link>
{canViewManagementPages && (
<>
<Link to="/customers" className={getNavClass('/customers')} title={t('nav.customers')}>
@@ -203,6 +210,14 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
</button>
{isPluginsOpen && !isCollapsed && (
<div className="ml-4 mt-1 space-y-1 border-l border-white/20 pl-4">
<Link
to="/plugins/marketplace"
className={`flex items-center gap-3 py-2 text-sm font-medium rounded-lg transition-colors px-3 ${location.pathname === '/plugins/marketplace' ? 'bg-white/10 text-white' : 'text-white/60 hover:text-white hover:bg-white/5'}`}
title={t('nav.marketplace', 'Marketplace')}
>
<Store size={16} className="shrink-0" />
<span>{t('nav.marketplace', 'Marketplace')}</span>
</Link>
<Link
to="/plugins/my-plugins"
className={`flex items-center gap-3 py-2 text-sm font-medium rounded-lg transition-colors px-3 ${location.pathname === '/plugins/my-plugins' ? 'bg-white/10 text-white' : 'text-white/60 hover:text-white hover:bg-white/5'}`}