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