Skip to content
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,7 +36,7 @@ export function ActiveGroupSwitcher({
variant="outline"
size={isMobile ? "sm" : "default"}
className={className}
aria-label={isMobile ? `Active scope: ${currentLabel}` : undefined}
aria-label={`Active scope: ${currentLabel}`}
>
<CurrentIcon className="h-4 w-4" />
{!isMobile && (
Expand Down
34 changes: 34 additions & 0 deletions src/hooks/useScheduleVoteScope.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
import { useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import { useActiveScope } from "@/contexts/ActiveScopeContext";
import { groupMembersQuery } from "@/api/groups/useGroupMembers";
import type { VoteScope } from "@/lib/voteScope";

/**
* Resolves the Schedule tab's vote-chip scope from the global Active Scope
* (the navbar switcher) — Everyone / Me / Active Group — plus the Active
* Group's member ids for group-scope filtering, `undefined` while loading
* (or when the scope isn't "group").
*/
export function useScheduleVoteScope() {
const { current } = useActiveScope();
const groupId = current.kind === "group" ? current.groupId : undefined;

const { data: members } = useQuery({
...groupMembersQuery(groupId ?? ""),
enabled: !!groupId,
});

const groupMemberIds = useMemo(
() =>
members ? new Set(members.map((member) => member.user_id)) : undefined,
[members],
);

const voteScope: VoteScope = current.kind;

return {
voteScope,
groupMemberIds: current.kind === "group" ? groupMemberIds : undefined,
};
}
176 changes: 155 additions & 21 deletions src/lib/scheduleFilter.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -202,15 +202,21 @@ describe("filterScheduleDays", () => {
id: "stage-1",
name: "Main Stage",
stage_order: 1,
sets: [makeSet({ id: "set-1" }), makeSet({ id: "set-2" })],
sets: [
makeSet({
id: "set-1",
votes: [{ user_id: "me", vote_type: 2 }],
}),
makeSet({ id: "set-2" }),
],
},
],
}),
];

const result = filterScheduleDays(
days,
baseCriteria({ voteTypes: [], userVotes: { "set-1": 2 } }),
baseCriteria({ voteTypes: [], currentUserId: "me" }),
TIMEZONE,
);

Expand All@@ -220,7 +226,7 @@ describe("filterScheduleDays", () => {
]);
});

it("keeps only sets matching a single selected vote type", () => {
it("keeps only sets matching a single selected vote type (me scope)", () => {
const days = [
makeDay({
stages: [
Expand All@@ -229,8 +235,14 @@ describe("filterScheduleDays", () => {
name: "Main Stage",
stage_order: 1,
sets: [
makeSet({ id: "must-go-set" }),
makeSet({ id: "interested-set" }),
makeSet({
id: "must-go-set",
votes: [{ user_id: "me", vote_type: 2 }],
}),
makeSet({
id: "interested-set",
votes: [{ user_id: "me", vote_type: 1 }],
}),
],
},
],
Expand All@@ -241,7 +253,8 @@ describe("filterScheduleDays", () => {
days,
baseCriteria({
voteTypes: ["mustGo"],
userVotes: { "must-go-set": 2, "interested-set": 1 },
voteScope: "me",
currentUserId: "me",
}),
TIMEZONE,
);
Expand All@@ -260,9 +273,18 @@ describe("filterScheduleDays", () => {
name: "Main Stage",
stage_order: 1,
sets: [
makeSet({ id: "must-go-set" }),
makeSet({ id: "interested-set" }),
makeSet({ id: "wont-go-set" }),
makeSet({
id: "must-go-set",
votes: [{ user_id: "me", vote_type: 2 }],
}),
makeSet({
id: "interested-set",
votes: [{ user_id: "me", vote_type: 1 }],
}),
makeSet({
id: "wont-go-set",
votes: [{ user_id: "me", vote_type: -1 }],
}),
],
},
],
Expand All@@ -273,11 +295,7 @@ describe("filterScheduleDays", () => {
days,
baseCriteria({
voteTypes: ["mustGo", "interested"],
userVotes: {
"must-go-set": 2,
"interested-set": 1,
"wont-go-set": -1,
},
currentUserId: "me",
}),
TIMEZONE,
);
Expand All@@ -304,14 +322,14 @@ describe("filterScheduleDays", () => {

const result = filterScheduleDays(
days,
baseCriteria({ voteTypes: ["mustGo"], userVotes: {} }),
baseCriteria({ voteTypes: ["mustGo"], currentUserId: "me" }),
TIMEZONE,
);

expect(result[0].stages[0].sets).toHaveLength(0);
});

it("is inert when userVotes is undefined (no viewer identity)", () => {
it("is inert when currentUserId is undefined (no viewer identity)", () => {
const days = [
makeDay({
stages: [
Expand All@@ -327,7 +345,7 @@ describe("filterScheduleDays", () => {

const result = filterScheduleDays(
days,
baseCriteria({ voteTypes: ["mustGo"], userVotes: undefined }),
baseCriteria({ voteTypes: ["mustGo"], currentUserId: undefined }),
TIMEZONE,
);

Expand All@@ -345,7 +363,12 @@ describe("filterScheduleDays", () => {
id: "stage-1",
name: "Main Stage",
stage_order: 1,
sets: [makeSet({ id: "weird-vote-set" })],
sets: [
makeSet({
id: "weird-vote-set",
votes: [{ user_id: "me", vote_type: 0 }],
}),
],
},
],
}),
Expand All@@ -355,7 +378,7 @@ describe("filterScheduleDays", () => {
days,
baseCriteria({
voteTypes: ["mustGo"],
userVotes: { "weird-vote-set": 0 },
currentUserId: "me",
}),
TIMEZONE,
);
Expand All@@ -371,7 +394,12 @@ describe("filterScheduleDays", () => {
id: "stage-1",
name: "Main Stage",
stage_order: 1,
sets: [makeSet({ id: "not-in-map" })],
sets: [
makeSet({
id: "not-in-map",
votes: [{ user_id: "someone-else", vote_type: 2 }],
}),
],
},
],
}),
Expand All@@ -381,13 +409,118 @@ describe("filterScheduleDays", () => {
days,
baseCriteria({
voteTypes: ["mustGo"],
userVotes: { "some-other-set": 2 },
currentUserId: "me",
}),
TIMEZONE,
);

expect(result[0].stages[0].sets).toHaveLength(0);
});

it("under everyone scope, matches a set voted on by any user at all", () => {
const days = [
makeDay({
stages: [
{
id: "stage-1",
name: "Main Stage",
stage_order: 1,
sets: [
makeSet({
id: "stranger-must-go",
votes: [{ user_id: "total-stranger", vote_type: 2 }],
}),
makeSet({ id: "unvoted" }),
],
},
],
}),
];

const result = filterScheduleDays(
days,
baseCriteria({
voteTypes: ["mustGo"],
voteScope: "everyone",
currentUserId: "me",
}),
TIMEZONE,
);

expect(result[0].stages[0].sets.map((s) => s.id)).toEqual([
"stranger-must-go",
]);
});

it("under group scope, matches a set voted on by any group member", () => {
const days = [
makeDay({
stages: [
{
id: "stage-1",
name: "Main Stage",
stage_order: 1,
sets: [
makeSet({
id: "group-must-go",
votes: [{ user_id: "teammate", vote_type: 2 }],
}),
makeSet({
id: "outsider-must-go",
votes: [{ user_id: "stranger", vote_type: 2 }],
}),
],
},
],
}),
];

const result = filterScheduleDays(
days,
baseCriteria({
voteTypes: ["mustGo"],
voteScope: "group",
currentUserId: "me",
groupMemberIds: new Set(["me", "teammate"]),
}),
TIMEZONE,
);

expect(result[0].stages[0].sets.map((s) => s.id)).toEqual([
"group-must-go",
]);
});

it("is inert under group scope when groupMemberIds is undefined (still loading)", () => {
const days = [
makeDay({
stages: [
{
id: "stage-1",
name: "Main Stage",
stage_order: 1,
sets: [makeSet({ id: "set-1" }), makeSet({ id: "set-2" })],
},
],
}),
];

const result = filterScheduleDays(
days,
baseCriteria({
voteTypes: ["mustGo"],
voteScope: "group",
currentUserId: "me",
groupMemberIds: undefined,
}),
TIMEZONE,
);

expect(result[0].stages[0].sets.map((s) => s.id)).toEqual([
"set-1",
"set-2",
]);
});
});

describe("combinations", () => {
Expand DownExpand Up@@ -466,6 +599,7 @@ function makeSet(overrides: Partial<ScheduleSet> = {}): ScheduleSet {
id: "set-1",
name: "A set",
artists: [],
votes: [],
startTime: new Date("2024-07-15T10:00:00Z"),
...overrides,
};
Expand Down
Loading
Loading