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

@@ -7,8 +7,10 @@ from rest_framework import viewsets, status
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from .models import Resource, Event, Participant
from .serializers import ResourceSerializer, EventSerializer, ParticipantSerializer
from .serializers import ResourceSerializer, EventSerializer, ParticipantSerializer, CustomerSerializer, ServiceSerializer
from .models import Service
from core.permissions import HasQuota
from smoothschedule.users.models import User
class ResourceViewSet(viewsets.ModelViewSet):
@@ -52,17 +54,46 @@ class EventViewSet(viewsets.ModelViewSet):
- EventSerializer.validate() automatically checks resource availability
- If resource capacity exceeded, returns 400 Bad Request
- See schedule/services.py AvailabilityService for logic
Query Parameters:
- start_date: Filter events starting on or after this date (ISO format)
- end_date: Filter events starting before this date (ISO format)
- status: Filter by event status
"""
queryset = Event.objects.all()
serializer_class = EventSerializer
# TODO: Re-enable authentication for production
permission_classes = [AllowAny] # Temporarily allow unauthenticated access for development
filterset_fields = ['status', 'start_time', 'end_time']
filterset_fields = ['status']
search_fields = ['title', 'notes']
ordering_fields = ['start_time', 'end_time', 'created_at']
ordering = ['start_time']
def get_queryset(self):
"""
Filter events by date range if start_date and end_date are provided.
"""
queryset = Event.objects.all()
# Filter by date range
start_date = self.request.query_params.get('start_date')
end_date = self.request.query_params.get('end_date')
if start_date:
from django.utils.dateparse import parse_datetime
start_dt = parse_datetime(start_date)
if start_dt:
queryset = queryset.filter(start_time__gte=start_dt)
if end_date:
from django.utils.dateparse import parse_datetime
end_dt = parse_datetime(end_date)
if end_dt:
queryset = queryset.filter(start_time__lt=end_dt)
return queryset
def perform_create(self, serializer):
"""
Create event with automatic availability validation.
@@ -90,14 +121,93 @@ class EventViewSet(viewsets.ModelViewSet):
class ParticipantViewSet(viewsets.ModelViewSet):
"""
API endpoint for managing Event Participants.
Allows adding/removing participants (Resources, Staff, Customers)
to/from events via the GenericForeignKey pattern.
"""
queryset = Participant.objects.all()
serializer_class = ParticipantSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['event', 'role', 'content_type']
ordering_fields = ['created_at']
ordering = ['-created_at']
class CustomerViewSet(viewsets.ModelViewSet):
"""
API endpoint for managing Customers.
Customers are Users with role=CUSTOMER belonging to the current tenant.
"""
serializer_class = CustomerSerializer
# TODO: Re-enable authentication for production
permission_classes = [AllowAny] # Temporarily allow unauthenticated access for development
filterset_fields = ['is_active']
search_fields = ['email', 'first_name', 'last_name']
ordering_fields = ['email', 'created_at']
ordering = ['email']
def get_queryset(self):
"""
Return customers for the current tenant.
Customers are Users with role=CUSTOMER.
For now, return all customers. When authentication is enabled,
filter by the user's tenant.
"""
queryset = User.objects.filter(role=User.Role.CUSTOMER)
# Filter by tenant if user is authenticated and has a tenant
# TODO: Re-enable this when authentication is enabled
# if self.request.user.is_authenticated and self.request.user.tenant:
# queryset = queryset.filter(tenant=self.request.user.tenant)
# Apply status filter if provided
status_filter = self.request.query_params.get('status')
if status_filter:
if status_filter == 'Active':
queryset = queryset.filter(is_active=True)
elif status_filter == 'Inactive':
queryset = queryset.filter(is_active=False)
# Apply search filter if provided
search = self.request.query_params.get('search')
if search:
from django.db.models import Q
queryset = queryset.filter(
Q(email__icontains=search) |
Q(first_name__icontains=search) |
Q(last_name__icontains=search)
)
return queryset
class ServiceViewSet(viewsets.ModelViewSet):
"""
API endpoint for managing Services.
Services are the offerings a business provides (e.g., Haircut, Massage).
"""
queryset = Service.objects.filter(is_active=True)
serializer_class = ServiceSerializer
# TODO: Re-enable authentication for production
permission_classes = [AllowAny] # Temporarily allow unauthenticated access for development
filterset_fields = ['is_active']
search_fields = ['name', 'description']
ordering_fields = ['name', 'price', 'duration', 'created_at']
ordering = ['name']
def get_queryset(self):
"""Return services, optionally including inactive ones."""
queryset = Service.objects.all()
# By default only show active services
show_inactive = self.request.query_params.get('show_inactive', 'false')
if show_inactive.lower() != 'true':
queryset = queryset.filter(is_active=True)
return queryset