from datetime import timedelta from django import forms from django.utils import timezone from .models import ActivityItem, QuoteLine, SalesWorkspace class StyledFormMixin: def _apply_styles(self): for name, field in self.fields.items(): widget = field.widget css_class = "form-select" if isinstance(widget, forms.Select) else "form-control" existing = widget.attrs.get("class", "") widget.attrs["class"] = f"{existing} {css_class} workspace-input".strip() class WorkspaceIntakeForm(StyledFormMixin, forms.ModelForm): next_meeting_at = forms.DateTimeField( required=False, widget=forms.DateTimeInput(attrs={"type": "datetime-local"}), ) class Meta: model = SalesWorkspace fields = [ "customer_name", "project_name", "project_number", "zipcode", "address", "city", "contact_name", "contact_email", "contact_phone", "opportunity_title", "estimated_value", "layout_template", "summary", "next_step", "next_meeting_at", ] widgets = { "summary": forms.Textarea(attrs={"rows": 3}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._apply_styles() placeholders = { "customer_name": "Van der Meer Construction", "project_name": "Renovation of showroom", "project_number": "PRJ-24018", "zipcode": "3011 AA", "address": "Binnenweg 18", "city": "Rotterdam", "contact_name": "Eva Jansen", "contact_email": "eva@example.com", "contact_phone": "+31 6 12345678", "opportunity_title": "Lighting upgrade quotation", "estimated_value": "18500", "summary": "Existing customer requesting quick quote with site visit.", "next_step": "Confirm site meeting and draft first quotation.", } for name, placeholder in placeholders.items(): self.fields[name].widget.attrs.setdefault("placeholder", placeholder) self.fields["layout_template"].widget.attrs.setdefault("aria-label", "Workspace template") def clean_next_meeting_at(self): meeting = self.cleaned_data.get("next_meeting_at") if meeting and meeting < timezone.now(): raise forms.ValidationError("Choose a future time for the next meeting.") return meeting class StageUpdateForm(StyledFormMixin, forms.Form): stage = forms.ChoiceField(choices=SalesWorkspace.Stage.choices) def __init__(self, *args, **kwargs): current_stage = kwargs.pop("current_stage", None) super().__init__(*args, **kwargs) self._apply_styles() if current_stage: self.fields["stage"].initial = current_stage class QuoteLineForm(StyledFormMixin, forms.ModelForm): class Meta: model = QuoteLine fields = ["product_name", "description", "quantity", "unit_price"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._apply_styles() self.fields["product_name"].widget.attrs.setdefault("placeholder", "Product or service") self.fields["description"].widget.attrs.setdefault("placeholder", "Optional scope note") self.fields["quantity"].widget.attrs.setdefault("min", 1) self.fields["unit_price"].widget.attrs.setdefault("min", 0) self.fields["unit_price"].widget.attrs.setdefault("step", "0.01") class ActivityForm(StyledFormMixin, forms.ModelForm): due_at = forms.DateTimeField( widget=forms.DateTimeInput(attrs={"type": "datetime-local"}) ) class Meta: model = ActivityItem fields = ["title", "activity_type", "due_at", "owner", "notes"] widgets = { "notes": forms.Textarea(attrs={"rows": 3}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._apply_styles() self.fields["title"].widget.attrs.setdefault("placeholder", "Schedule follow-up or task") self.fields["owner"].widget.attrs.setdefault("placeholder", "Rep or team owner") self.fields["notes"].widget.attrs.setdefault("placeholder", "Talking points, reminders, dependencies") self.fields["due_at"].initial = (timezone.now() + timedelta(days=1)).strftime("%Y-%m-%dT%H:%M") def clean_due_at(self): due_at = self.cleaned_data["due_at"] if due_at < timezone.now() - timedelta(minutes=5): raise forms.ValidationError("Activity time should be now or in the future.") return due_at