From 3a8138006eb7b25e920a99461cd4660388fc11f7 Mon Sep 17 00:00:00 2001
From: Harsh Tandiya
Date: Fri, 14 Aug 2026 04:45:13 +0530
Subject: [PATCH] feat(dashboard): a team's events at /manage/team/events
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The team dashboard could show its members but not its events, so the sidebar's
"See all events" and the Overview preview both led to the personal feed. This
adds the team's own timeline, reached from a new Team > Events sidebar item,
and points the Overview link at it.
Every event on the page belongs to the team, so the per-card Manage button
carries no information here — EventCard takes a `showManage` prop to switch it
off. The rows come from the events feed the personal page already loads,
filtered by team: a member sees every event their team hosts, so no new
endpoint is needed.
Registrations, Sponsors and More leave the sidebar. They were placeholders for
pages that are not planned. Renames the personal item to "My Events" to tell it
apart from the team's.
Co-Authored-By: Claude Opus 5
---
.../components/dashboard/events/EventCard.vue | 30 ++++++++-----
.../dashboard/teams/TeamPageHeader.vue | 2 +-
dashboard/src/layouts/ManagerLayout.vue | 6 +--
dashboard/src/pages/manage/MyEvents.vue | 2 +-
.../src/pages/manage/teams/TeamEvents.vue | 42 +++++++++++++++++++
.../src/pages/manage/teams/TeamOverview.vue | 2 +-
dashboard/src/router.ts | 5 +++
e2e/tests/manage-access.spec.ts | 4 +-
8 files changed, 74 insertions(+), 19 deletions(-)
create mode 100644 dashboard/src/pages/manage/teams/TeamEvents.vue
diff --git a/dashboard/src/components/dashboard/events/EventCard.vue b/dashboard/src/components/dashboard/events/EventCard.vue
index 863cf46a..2dd4e254 100644
--- a/dashboard/src/components/dashboard/events/EventCard.vue
+++ b/dashboard/src/components/dashboard/events/EventCard.vue
@@ -2,12 +2,18 @@
import type { MyEvent } from "@/types";
import { dayLabel } from "@/utils/dateLabels";
import { bannerPattern } from "@/utils/eventBanner";
-import { Avatar } from "frappe-ui";
+import { Avatar, Button } from "frappe-ui";
import { computed } from "vue";
// The Events page files cards under a date heading; a standalone list has to
// carry the date on the card itself.
-const props = defineProps<{ event: MyEvent; showDate?: boolean }>();
+const props = withDefaults(
+ defineProps<{ event: MyEvent; showDate?: boolean; showManage?: boolean }>(),
+ { showManage: true }
+);
+
+// Two gates: the caller has to want the button, and only a host has anything to manage.
+const canManage = computed(() => props.showManage && props.event.is_host);
// Times arrive as a serialized timedelta ("9:00:00"), so the hour needs padding.
const startTime = computed((): string => {
@@ -69,14 +75,18 @@ const venue = computed(() => {
-
-
- {{ venue.label }}
-
+
+
+
+ {{ venue.label }}
+
+
+
+
diff --git a/dashboard/src/components/dashboard/teams/TeamPageHeader.vue b/dashboard/src/components/dashboard/teams/TeamPageHeader.vue
index 5905e5f5..3b7425e8 100644
--- a/dashboard/src/components/dashboard/teams/TeamPageHeader.vue
+++ b/dashboard/src/components/dashboard/teams/TeamPageHeader.vue
@@ -13,7 +13,7 @@ const items = computed(() => {
-
+
diff --git a/dashboard/src/layouts/ManagerLayout.vue b/dashboard/src/layouts/ManagerLayout.vue
index e269c6f7..8b2b3081 100644
--- a/dashboard/src/layouts/ManagerLayout.vue
+++ b/dashboard/src/layouts/ManagerLayout.vue
@@ -20,7 +20,7 @@ const route = useRoute();
const isActive = (to: string) => route.path === to;
const personalItems = [
- { label: "Events", icon: "lucide-calendar-days", to: "/manage/events" },
+ { label: "My Events", icon: "lucide-calendar-days", to: "/manage/events" },
{ label: "My Tickets", icon: "lucide-ticket", to: "/manage/tickets" },
{ label: "Talk Proposals", icon: "lucide-file-text", to: "/manage/proposals" },
{ label: "Sponsorship", icon: "lucide-handshake", to: "/manage/sponsorship" },
@@ -30,10 +30,8 @@ const personalItems = [
// so they are fixed and need no team loaded to render.
const teamItems = [
{ label: "Overview", icon: "lucide-layout-dashboard", to: "/manage/team/overview" },
+ { label: "Events", icon: "lucide-calendar-days", to: "/manage/team/events" },
{ label: "Members", icon: "lucide-users-round", to: "/manage/team/members" },
- { label: "Registrations", icon: "lucide-users", to: "/manage/registrations" },
- { label: "Sponsors", icon: "lucide-badge-dollar-sign", to: "/manage/sponsors" },
- { label: "More", icon: "lucide-ellipsis", to: "/manage/more" },
];
diff --git a/dashboard/src/pages/manage/MyEvents.vue b/dashboard/src/pages/manage/MyEvents.vue
index 56792721..ce0be852 100644
--- a/dashboard/src/pages/manage/MyEvents.vue
+++ b/dashboard/src/pages/manage/MyEvents.vue
@@ -17,7 +17,7 @@ const months = computed(() => groupEventsByMonth(myEvents.data?.[tab.value] || [
+import TimelineList from "@/components/dashboard/TimelineList.vue";
+import EventCard from "@/components/dashboard/events/EventCard.vue";
+import TeamPageHeader from "@/components/dashboard/teams/TeamPageHeader.vue";
+import { useMyEvents } from "@/data/events";
+import { currentTeam } from "@/data/teams";
+import { groupEventsByMonth } from "@/utils/eventGroups";
+import type { TimelineTab } from "@/utils/timelineTabs";
+import { computed, ref } from "vue";
+
+const myEvents = useMyEvents();
+
+const tab = ref("upcoming");
+
+// Reuses the feed the Events page already loads: a member sees every event their own
+// team hosts, so filtering that feed by team is the team's whole calendar.
+const months = computed(() =>
+ groupEventsByMonth(
+ (myEvents.data?.[tab.value] || []).filter(
+ (event) => event.team === currentTeam.value?.name
+ )
+ )
+);
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dashboard/src/pages/manage/teams/TeamOverview.vue b/dashboard/src/pages/manage/teams/TeamOverview.vue
index 509cc8d9..c01a14a4 100644
--- a/dashboard/src/pages/manage/teams/TeamOverview.vue
+++ b/dashboard/src/pages/manage/teams/TeamOverview.vue
@@ -72,7 +72,7 @@ const hiddenMemberCount = computed(() =>
variant="ghost"
label="See all events"
class="w-fit"
- :route="{ name: 'events' }"
+ :route="{ name: 'team-events' }"
/>
diff --git a/dashboard/src/router.ts b/dashboard/src/router.ts
index fbdf8398..5db00d1f 100644
--- a/dashboard/src/router.ts
+++ b/dashboard/src/router.ts
@@ -48,6 +48,11 @@ const routes: RouteRecordRaw[] = [
name: "team-overview",
component: () => import("@/pages/manage/teams/TeamOverview.vue"),
},
+ {
+ path: "team/events",
+ name: "team-events",
+ component: () => import("@/pages/manage/teams/TeamEvents.vue"),
+ },
{
path: "team/members",
name: "team-members",
diff --git a/e2e/tests/manage-access.spec.ts b/e2e/tests/manage-access.spec.ts
index 76d93800..096d08ab 100644
--- a/e2e/tests/manage-access.spec.ts
+++ b/e2e/tests/manage-access.spec.ts
@@ -13,9 +13,9 @@ test.describe("Manage access", () => {
test("routes a sidebar item with no page yet to the placeholder", async ({ page }) => {
await page.goto("/b/manage/events");
- await page.getByRole("link", { name: "Registrations" }).click();
+ await page.getByRole("link", { name: "Sponsorship" }).click();
- await expect(page).toHaveURL(/\/b\/manage\/registrations$/);
+ await expect(page).toHaveURL(/\/b\/manage\/sponsorship$/);
await expect(page.getByText("Work in progress")).toBeVisible();
});