Add scheduler improvements, API endpoints, and month calendar view

Backend:
- Add /api/customers/ endpoint (CustomerViewSet, CustomerSerializer)
- Add /api/services/ endpoint with Service model and migrations
- Add Resource.type field (STAFF, ROOM, EQUIPMENT) with migration
- Fix EventSerializer to return resource_id, customer_id, service_id
- Add date range filtering to EventViewSet (start_date, end_date params)
- Add create_demo_appointments management command
- Set default brand colors in business API

Frontend:
- Add calendar grid view for month mode in OwnerScheduler
- Fix sidebar navigation active link contrast (bg-white/10)
- Add default primaryColor/secondaryColor fallbacks in useBusiness
- Disable WebSocket (backend not implemented) to stop reconnect loop
- Fix Resource.type.toLowerCase() error by adding type to backend

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-11-27 20:09:04 -05:00
parent 38c43d3f27
commit 373257469b
38 changed files with 977 additions and 2111 deletions

View File

@@ -35,6 +35,9 @@ interface UseAppointmentWebSocketOptions {
onError?: (error: Event) => void;
}
// WebSocket is not yet implemented in the backend - disable for now
const WEBSOCKET_ENABLED = false;
/**
* Transform backend appointment format to frontend format
*/
@@ -60,6 +63,9 @@ function transformAppointment(data: WebSocketMessage['appointment']): Appointmen
*/
export function useAppointmentWebSocket(options: UseAppointmentWebSocketOptions = {}) {
const { enabled = true, onConnected, onDisconnected, onError } = options;
// Early return if WebSocket is globally disabled
const effectivelyEnabled = enabled && WEBSOCKET_ENABLED;
const queryClient = useQueryClient();
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
@@ -138,7 +144,7 @@ export function useAppointmentWebSocket(options: UseAppointmentWebSocketOptions
// Main effect to manage WebSocket connection
// Only depends on `enabled` - other values are read from refs or called as functions
useEffect(() => {
if (!enabled) {
if (!effectivelyEnabled) {
return;
}
@@ -285,7 +291,7 @@ export function useAppointmentWebSocket(options: UseAppointmentWebSocketOptions
setIsConnected(false);
};
}, [enabled]); // Only re-run when enabled changes
}, [effectivelyEnabled]); // Only re-run when enabled changes
const reconnect = useCallback(() => {
isCleaningUpRef.current = false;

View File

@@ -30,8 +30,8 @@ export const useCurrentBusiness = () => {
id: String(data.id),
name: data.name,
subdomain: data.subdomain,
primaryColor: data.primary_color,
secondaryColor: data.secondary_color,
primaryColor: data.primary_color || '#3B82F6', // Blue-500 default
secondaryColor: data.secondary_color || '#1E40AF', // Blue-800 default
logoUrl: data.logo_url,
whitelabelEnabled: data.whitelabel_enabled,
plan: data.tier, // Map tier to plan

View File

@@ -43,6 +43,7 @@ export const useCustomers = (filters?: CustomerFilters) => {
user_data: c.user_data, // Include user_data for masquerading
}));
},
retry: false, // Don't retry on 404 - endpoint may not exist yet
});
};

View File

@@ -24,6 +24,7 @@ export const useServices = () => {
description: s.description || '',
}));
},
retry: false, // Don't retry on 404 - endpoint may not exist yet
});
};
@@ -45,6 +46,7 @@ export const useService = (id: string) => {
};
},
enabled: !!id,
retry: false,
});
};