feat: Add real-time ticket updates via WebSocket and staff permission control

WebSocket Updates:
- Create useTicketWebSocket hook for real-time ticket list updates
- Hook invalidates React Query cache when tickets are created/updated
- Shows toast notifications for new tickets and comments
- Auto-reconnect with exponential backoff

Staff Permissions:
- Add can_access_tickets() method to User model
- Owners and managers always have ticket access
- Staff members need explicit can_access_tickets permission
- Update Sidebar to conditionally show Tickets menu based on permission
- Add can_access_tickets to API user response

Backend Updates:
- Update ticket signals to broadcast updates to all relevant users
- Check ticket access permission in views

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-11-28 05:44:39 -05:00
parent c400e8a722
commit 9dabb0cb83
9 changed files with 253 additions and 28 deletions

View File

@@ -51,6 +51,8 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
const canViewAdminPages = role === 'owner' || role === 'manager';
const canViewManagementPages = role === 'owner' || role === 'manager' || role === 'staff';
const canViewSettings = role === 'owner';
// Tickets: owners/managers always, staff only with permission
const canViewTickets = role === 'owner' || role === 'manager' || (role === 'staff' && user.can_access_tickets);
const getDashboardLink = () => {
if (role === 'resource') return '/';
@@ -135,13 +137,16 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
<ClipboardList size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.resources')}</span>}
</Link>
<Link to="/tickets" className={getNavClass('/tickets')} title={t('nav.tickets')}>
<Ticket size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.tickets')}</span>}
</Link>
</>
)}
{canViewTickets && (
<Link to="/tickets" className={getNavClass('/tickets')} title={t('nav.tickets')}>
<Ticket size={20} className="shrink-0" />
{!isCollapsed && <span>{t('nav.tickets')}</span>}
</Link>
)}
{canViewAdminPages && (
<>
{/* Payments link: always visible for owners, only visible for others if enabled */}