Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<contribution-user-row
:contribution-period-id="contributionPeriodId"
:contributions="contributions"
:period-member-user-ids="periodMemberUserIds"
:disabled="disabled"
:user="user"
@update:contribution="contributionChanged"
Expand Down Expand Up @@ -88,16 +89,27 @@ const props = withDefaults(defineProps<{
users: UserDetailResponse[]
contributionPeriodId: number
contributions?: ContributionResponse[]
periodMemberUserIds?: Set<number>
disabled?: boolean
startOpen?: boolean
}>(), {
panelKey: "",
contributions: () => [],
periodMemberUserIds: () => new Set<number>(),
disabled: false,
startOpen: false,
})

const {title, panelKey, users, contributions, disabled, contributionPeriodId, startOpen} = toRefs(props)
const {
title,
panelKey,
users,
contributions,
periodMemberUserIds,
disabled,
contributionPeriodId,
startOpen,
} = toRefs(props)

const emit = defineEmits<{
(e: "delete:contribution", userId: number): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@
>
<div class="d-flex align-center mr-4">
<v-chip
v-if="!!user?.roles?.length"
:color="user.roles.includes('MEMBER') ? 'primary' : 'grey'"
:color="wasMemberDuringPeriod ? 'primary' : 'grey'"
class="mr-3 d-flex justify-center align-center text-capitalize"
size="small"
style="width: 60px"
variant="flat"
>
{{ user.roles.includes("MEMBER") ? "Member" : "User" }}
{{ wasMemberDuringPeriod ? "Member" : "User" }}
</v-chip>

<v-btn
Expand Down Expand Up @@ -53,9 +52,11 @@ const props = withDefaults(defineProps<{
user: UserDetailResponse
contributionPeriodId: number
contributions?: Array<ContributionResponse>
periodMemberUserIds?: Set<number>
disabled?: boolean
}>(), {
contributions: () => [],
periodMemberUserIds: () => new Set<number>(),
disabled: false,
})

Expand All @@ -66,6 +67,8 @@ const emit = defineEmits<{

const saving = ref(false)

const wasMemberDuringPeriod = computed(() => props.periodMemberUserIds.has(props.user.id))

const contribution = computed<ContributionResponse | undefined>(() =>
props.contributions.find(
(c) => c.userId === props.user.id && c.contributionPeriodId === props.contributionPeriodId,
Expand Down
19 changes: 17 additions & 2 deletions services/frontend/src/pages/management/ContributionManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<contribution-user-list
:contribution-period-id="selectedPeriodId"
:contributions="contributions"
:period-member-user-ids="periodMemberUserIds"
:disabled="!selectedPeriodId"
panel-key="paid"
:users="usersPaid"
Expand All @@ -24,6 +25,7 @@
<contribution-user-list
:contribution-period-id="selectedPeriodId"
:contributions="contributions"
:period-member-user-ids="periodMemberUserIds"
:disabled="!selectedPeriodId"
panel-key="unpaid"
:users="usersUnpaid"
Expand All @@ -47,12 +49,14 @@ import {
type ContributionPeriodResponse,
type ContributionResponse,
findContributionsByPeriodId,
findMemberships,
findUsers,
type UserDetailResponse,
} from "@/services/api"

const users = ref<UserDetailResponse[]>([])
const contributions = ref<ContributionResponse[]>([])
const periodMemberUserIds = ref<Set<number>>(new Set())

const selectedPeriodId = ref<number>(0)

Expand Down Expand Up @@ -106,14 +110,25 @@ const contributionPeriodChanged = async (newPeriod?: ContributionPeriodResponse)
if (!periodId) {
selectedPeriodId.value = 0
contributions.value = []
periodMemberUserIds.value = new Set()
return
}

selectedPeriodId.value = periodId
contributions.value = []
periodMemberUserIds.value = new Set()

const resp = await findContributionsByPeriodId({path: {periodId}})
if (selectedPeriodId.value === periodId) contributions.value = resp.data ?? []
const [contributionsResp, membershipsResp] = await Promise.all([
findContributionsByPeriodId({path: {periodId}}),
findMemberships({query: {from: newPeriod.startDate, to: newPeriod.endDate}}),
])

if (selectedPeriodId.value !== periodId) return

contributions.value = contributionsResp.data ?? []
periodMemberUserIds.value = new Set(
(membershipsResp.data ?? []).map((membership) => membership.userId),
)
}

onMounted(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,46 @@ const vuetifyStubs = {
}

describe("ContributionUserList", () => {
it("renders list and forwards contribution update event", async () => {
it("renders list and forwards contribution update event and period membership set", async () => {
const wrapper = mount(ContributionUserList, {
props: {
title: "Contribution paid",
users,
contributionPeriodId: 2,
contributions: [],
periodMemberUserIds: new Set([1]),
startOpen: true,
},
global: {
stubs: {
...vuetifyStubs,
ContributionUserRow: {
template: "<button @click=\"$emit('update:contribution', { id: 99, userId: 1, contributionPeriodId: 2 })\">row</button>",
props: ["user", "periodMemberUserIds"],
template: `
<button
class="row"
:data-username="user.username"
:data-is-period-member="String(periodMemberUserIds.has(user.id))"
@click="$emit('update:contribution', { id: 99, userId: user.id, contributionPeriodId: 2 })"
>
row
</button>
`,
},
},
},
})

expect(wrapper.text()).toContain("Contribution paid")
await wrapper.find("button").trigger("click")

const rows = wrapper.findAll("button.row")
expect(rows).toHaveLength(2)
expect(rows[0].attributes("data-username")).toBe("lyndisluna")
expect(rows[0].attributes("data-is-period-member")).toBe("true")
expect(rows[1].attributes("data-username")).toBe("ariosfury")
expect(rows[1].attributes("data-is-period-member")).toBe("false")

await rows[0].trigger("click")
expect(wrapper.emitted("update:contribution")?.length).toBe(1)
})

Expand All @@ -75,7 +94,7 @@ describe("ContributionUserList", () => {
stubs: {
...vuetifyStubs,
ContributionUserRow: {
props: ["user"],
props: ["user", "periodMemberUserIds"],
template: "<div class='row-username'>{{ user.username }}</div>",
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ vi.mock("@/services/api", () => ({
deleteContribution: mockDeleteContribution,
}))

const mountRow = (props: Record<string, unknown>) =>
mount(ContributionUserRow, {
props: {
user: {id: 1, fullName: "Emma", username: "emma", roles: ["MEMBER"]},
contributionPeriodId: 9,
contributions: [],
...props,
},
global: {
stubs: {
VChip: {
props: ["color"],
template: "<span data-testid=\"membership-chip\" :data-color=\"color\"><slot /></span>",
},
},
},
})

describe("ContributionUserRow", () => {
beforeEach(() => {
vi.clearAllMocks()
Expand All @@ -20,13 +38,7 @@ describe("ContributionUserRow", () => {
})

it("marks and unmarks contribution", async () => {
const wrapper = mount(ContributionUserRow, {
props: {
user: {id: 1, fullName: "Emma", username: "emma", roles: ["MEMBER"]},
contributionPeriodId: 9,
contributions: [],
},
})
const wrapper = mountRow({})

await (wrapper.vm as any).markPaid()
expect(mockCreateContribution).toHaveBeenCalledWith({
Expand All @@ -48,4 +60,30 @@ describe("ContributionUserRow", () => {
},
})
})

it("renders membership chip from selected-period membership set instead of current roles", () => {
const periodMember = mountRow({
user: {id: 1, fullName: "Emma", username: "emma", roles: ["USER"]},
periodMemberUserIds: new Set([1]),
})

expect(periodMember.get("[data-testid='membership-chip']").text()).toBe("Member")
expect(periodMember.get("[data-testid='membership-chip']").attributes("data-color")).toBe("primary")

const currentMemberOnly = mountRow({
user: {id: 1, fullName: "Emma", username: "emma", roles: ["MEMBER"]},
periodMemberUserIds: new Set<number>(),
})

expect(currentMemberOnly.get("[data-testid='membership-chip']").text()).toBe("User")
expect(currentMemberOnly.get("[data-testid='membership-chip']").attributes("data-color")).toBe("grey")

const noRoles = mountRow({
user: {id: 1, fullName: "Emma", username: "emma", roles: []},
periodMemberUserIds: new Set<number>(),
})

expect(noRoles.get("[data-testid='membership-chip']").text()).toBe("User")
expect(noRoles.get("[data-testid='membership-chip']").attributes("data-color")).toBe("grey")
})
})
Loading
Loading