diff --git a/app/routes/events-team_.report.tsx b/app/routes/events-team_.report.tsx
new file mode 100644
index 0000000..eeb5659
--- /dev/null
+++ b/app/routes/events-team_.report.tsx
@@ -0,0 +1,118 @@
+import {
+  Container,
+  Heading,
+  Table,
+  TableCaption,
+  TableContainer,
+  Tbody,
+  Td,
+  Th,
+  Thead,
+  Tr,
+} from "@chakra-ui/react";
+import { useLoaderData } from "@remix-run/react";
+
+export async function loader({ context }: { context: RequestContext }) {
+  const { current_user: user } = context.data;
+
+  if (!user)
+    throw new Response(null, {
+      status: 401,
+    });
+
+  if (![1 << 4, 1 << 12].find((p) => user.permissions & p))
+    throw new Response(null, {
+      status: 403,
+    });
+
+  const now = new Date();
+  let month = now.getUTCMonth();
+  let year = now.getUTCFullYear();
+
+  if (month === 0) {
+    year--;
+    month = 12;
+  }
+
+  const eventMemberQuery = await context.env.D1.prepare(
+    "SELECT id, name FROM et_members;",
+  ).all();
+  const eventsQuery = await context.env.D1.prepare(
+    "SELECT answered_at, created_by, performed_at, reached_minimum_player_count, type FROM events WHERE month = ? AND year = ?;",
+  )
+    .bind(month, year)
+    .all();
+  const memberMap = Object.fromEntries(
+    eventMemberQuery.results.map((entry) => {
+      return [entry.id, { name: entry.name, points: 0 }];
+    }),
+  );
+
+  for (const event of eventsQuery.results as {
+    answered_at: number;
+    created_by: string;
+    performed_at: number;
+    reached_minimum_player_count: number;
+    type: string;
+  }[]) {
+    if (!memberMap[event.created_by]) continue;
+
+    if (event.performed_at) memberMap[event.created_by].points += 10;
+    else memberMap[event.created_by].points -= 10;
+
+    if (event.type === "gamenight" && event.reached_minimum_player_count)
+      memberMap[event.created_by].points += 10;
+    if (
+      event.type === "rotw" &&
+      event.answered_at - event.performed_at >= 86400000
+    )
+      memberMap[event.created_by].points += 10;
+  }
+
+  return memberMap;
+}
+
+export default function () {
+  const data = useLoaderData<typeof loader>() as {
+    [k: string]: { name: string; points: number };
+  };
+  const now = new Date();
+  let month = now.getUTCMonth();
+  let year = now.getUTCFullYear();
+
+  if (month === 0) {
+    month = 12;
+    year--;
+  }
+
+  return (
+    <Container maxW="container.lg">
+      <Heading>
+        Report for {month}/{year}
+      </Heading>
+      <TableContainer mt="16px">
+        <Table variant="simple">
+          <TableCaption>
+            Total points earned for the previous month
+          </TableCaption>
+          <Thead>
+            <Tr>
+              <Th>ID</Th>
+              <Th>Name</Th>
+              <Th>Points</Th>
+            </Tr>
+          </Thead>
+          <Tbody>
+            {Object.entries(data).map(([key, value]) => (
+              <Tr>
+                <Td>{key}</Td>
+                <Td>{value.name}</Td>
+                <Td>{value.points}</Td>
+              </Tr>
+            ))}
+          </Tbody>
+        </Table>
+      </TableContainer>
+    </Container>
+  );
+}