1"""Built-in admin views for core functionality."""
  2
  3import json
  4from typing import Any
  5
  6from plain.http import RedirectResponse, Response
  7
  8from .models import PinnedNavItem
  9from .views.base import AdminView
 10from .views.registry import registry
 11
 12MAX_PINNED_ITEMS = 6
 13
 14
 15class AdminIndexView(AdminView):
 16    template_name = "admin/index.html"
 17    title = "Dashboard"
 18
 19    def get(self) -> Response:
 20        # Slight hack to redirect to the first view that doesn't
 21        # require any url params...
 22        if views := registry.get_searchable_views():
 23            return RedirectResponse(list(views)[0].get_view_url())
 24
 25        return super().get()
 26
 27
 28class AdminSearchView(AdminView):
 29    template_name = "admin/search.html"
 30    title = "Search"
 31
 32    def get_template_context(self) -> dict[str, Any]:
 33        context = super().get_template_context()
 34        context["searchable_views"] = registry.get_searchable_views()
 35        context["global_search_query"] = self.request.query_params.get("query", "")
 36        return context
 37
 38
 39class PinNavView(AdminView):
 40    """Pin a navigation item for the current user."""
 41
 42    nav_section = None
 43
 44    def post(self) -> Response:
 45        view_slug = self.request.form_data.get("view_slug")
 46        if not view_slug:
 47            return Response("view_slug is required", status_code=400)
 48
 49        # Check if user has reached max pinned items
 50        current_count = PinnedNavItem.query.filter(user=self.user).count()
 51        if current_count >= MAX_PINNED_ITEMS:
 52            return Response(
 53                f"Maximum of {MAX_PINNED_ITEMS} pinned items reached",
 54                status_code=400,
 55            )
 56
 57        # Verify the view slug exists
 58        if not registry.get_view_by_slug(view_slug):
 59            return Response("Invalid view_slug", status_code=400)
 60
 61        max_order = (
 62            PinnedNavItem.query.filter(user=self.user)
 63            .order_by("-order")
 64            .values_list("order", flat=True)
 65            .first()
 66        )
 67        next_order = (max_order or 0) + 1
 68
 69        PinnedNavItem.query.get_or_create(
 70            user=self.user,
 71            view_slug=view_slug,
 72            defaults={"order": next_order},
 73        )
 74
 75        # Redirect back to current page (or referer)
 76        referer = self.request.headers.get("Referer", "/admin/")
 77        return RedirectResponse(referer)
 78
 79
 80class UnpinNavView(AdminView):
 81    """Unpin a navigation item for the current user."""
 82
 83    nav_section = None
 84
 85    def post(self) -> Response:
 86        view_slug = self.request.form_data.get("view_slug")
 87        if not view_slug:
 88            return Response("view_slug is required", status_code=400)
 89
 90        PinnedNavItem.query.filter(
 91            user=self.user,
 92            view_slug=view_slug,
 93        ).delete()
 94
 95        # Redirect back to current page (or referer)
 96        referer = self.request.headers.get("Referer", "/admin/")
 97        return RedirectResponse(referer)
 98
 99
100class ReorderPinnedView(AdminView):
101    """Reorder pinned navigation items."""
102
103    nav_section = None
104
105    def post(self) -> Response:
106        slugs_json = self.request.form_data.get("slugs")
107        if not slugs_json:
108            return Response("slugs is required", status_code=400)
109
110        try:
111            slugs = json.loads(slugs_json)
112        except json.JSONDecodeError:
113            return Response("Invalid slugs JSON", status_code=400)
114
115        # Only update slugs that exist and belong to this user
116        user_pinned = set(
117            PinnedNavItem.query.filter(user=self.user).values_list(
118                "view_slug", flat=True
119            )
120        )
121        for i, slug in enumerate(slugs):
122            if slug in user_pinned:
123                PinnedNavItem.query.filter(user=self.user, view_slug=slug).update(
124                    order=i
125                )
126
127        # No redirect needed for drag-and-drop reorder (called via fetch)
128        return Response("OK")
129
130
131class StyleGuideView(AdminView):
132    """Style guide showing available components and patterns."""
133
134    template_name = "admin/style.html"
135    title = "Style Guide"
136    nav_section = None