feat(c7): dashboard widgets as plugin contributions + contact counts via contacts contract
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
This commit is contained in:
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from app.plugins.base import BasePlugin
|
||||
from app.plugins.manifest import (
|
||||
FrontendDashboardWidget,
|
||||
FrontendDetailTab,
|
||||
FrontendMenuItem,
|
||||
FrontendPageRoute,
|
||||
@@ -40,6 +41,18 @@ class CalendarPlugin(BasePlugin):
|
||||
],
|
||||
events=[],
|
||||
migrations=["0001_initial.sql", "0002_add_deleted_at.sql"],
|
||||
dashboard_widgets=[
|
||||
FrontendDashboardWidget(
|
||||
id="calendar_upcoming",
|
||||
label_key="dashboard.calendarUpcoming",
|
||||
label="Upcoming Appointments",
|
||||
component="@/components/dashboard/CalendarUpcomingWidget",
|
||||
icon="Calendar",
|
||||
order=30,
|
||||
col_span=1,
|
||||
permission="calendar:read",
|
||||
),
|
||||
],
|
||||
permissions=[
|
||||
"calendar:read",
|
||||
"calendar:write",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"""Public contract for the contacts plugin.
|
||||
|
||||
Exposes the symbols that other core modules and plugins need without
|
||||
importing from internal modules directly (Block C7: dashboard counts).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.visibility import apply_visibility_filter
|
||||
from app.models.contact import Contact
|
||||
from app.plugins.builtins.contracts import get_contract_registry
|
||||
|
||||
|
||||
class ContactsContract:
|
||||
"""Public API surface for the contacts plugin."""
|
||||
|
||||
contract_name = "contacts"
|
||||
|
||||
@staticmethod
|
||||
async def get_counts(
|
||||
db: AsyncSession,
|
||||
tenant_id: Any,
|
||||
user_id: Any,
|
||||
is_system_admin: bool = False,
|
||||
) -> dict[str, int]:
|
||||
"""Return visibility-filtered contact/company/person counts."""
|
||||
queries = []
|
||||
for type_filter in (None, "company", "person"):
|
||||
query = select(func.count(Contact.id)).where(
|
||||
Contact.tenant_id == tenant_id,
|
||||
Contact.deleted_at.is_(None),
|
||||
)
|
||||
if type_filter is not None:
|
||||
query = query.where(Contact.type == type_filter)
|
||||
query = await apply_visibility_filter(
|
||||
db, query, "contact", Contact, user_id, tenant_id, is_system_admin
|
||||
)
|
||||
queries.append(query)
|
||||
|
||||
results = [((await db.execute(q)).scalar() or 0) for q in queries]
|
||||
return {
|
||||
"contacts": results[0],
|
||||
"companies": results[1],
|
||||
"persons": results[2],
|
||||
"total": results[0],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_function(cls, name: str):
|
||||
"""Return a callable exposed by this contract, or None if absent."""
|
||||
return getattr(cls, name, None)
|
||||
|
||||
|
||||
# ─── self-registration ───
|
||||
|
||||
_contract = ContactsContract()
|
||||
get_contract_registry().register("contacts", _contract)
|
||||
@@ -9,7 +9,11 @@ from __future__ import annotations
|
||||
import logging
|
||||
|
||||
from app.plugins.base import BasePlugin
|
||||
from app.plugins.manifest import PluginManifest, PluginRouteDef
|
||||
from app.plugins.manifest import (
|
||||
FrontendDashboardWidget,
|
||||
PluginManifest,
|
||||
PluginRouteDef,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -54,6 +58,18 @@ class ContactsPlugin(BasePlugin):
|
||||
],
|
||||
events=[],
|
||||
migrations=[],
|
||||
dashboard_widgets=[
|
||||
FrontendDashboardWidget(
|
||||
id="recent_contacts",
|
||||
label_key="dashboard.recentContacts",
|
||||
label="Recent Contacts",
|
||||
component="@/components/dashboard/RecentContactsWidget",
|
||||
icon="Users",
|
||||
order=10,
|
||||
col_span=2,
|
||||
permission="contacts:read",
|
||||
),
|
||||
],
|
||||
permissions=[
|
||||
"contacts:read",
|
||||
"contacts:write",
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
from app.plugins.base import BasePlugin
|
||||
from app.plugins.manifest import (
|
||||
CronJobContribution,
|
||||
FrontendDashboardWidget,
|
||||
FrontendMenuItem,
|
||||
FrontendPageRoute,
|
||||
PluginManifest,
|
||||
@@ -30,6 +31,18 @@ class TasksPlugin(BasePlugin):
|
||||
],
|
||||
events=[],
|
||||
migrations=["0001_initial.sql", "0002_unified_task_system.sql"],
|
||||
dashboard_widgets=[
|
||||
FrontendDashboardWidget(
|
||||
id="tasks_summary",
|
||||
label_key="dashboard.tasksSummary",
|
||||
label="Tasks Summary",
|
||||
component="@/components/dashboard/TasksSummaryWidget",
|
||||
icon="CheckSquare",
|
||||
order=20,
|
||||
col_span=1,
|
||||
permission="tasks:read",
|
||||
),
|
||||
],
|
||||
permissions=[
|
||||
"tasks:read",
|
||||
"tasks:write",
|
||||
|
||||
Reference in New Issue
Block a user