All files / packages/app/features/receipt-components receipt-item-payers.tsx

85% Statements 34/40
66.66% Branches 8/12
80% Functions 16/20
84.61% Lines 33/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118                                          901x 1272x 1272x 1272x 1272x 1272x 1272x   1009x 3726x 784x 862x 3726x 3726x 862x 1272x   2942x         1272x   1847x     1272x   1152x     1272x 1272x               3x     10x           2x         2x 2x   2x       3765x   3765x 3765x                                                 7452x   3726x        
import type React from "react";
 
import { useTranslation } from "react-i18next";
import { isNonNullish } from "remeda";
 
import { LoadablePeer } from "~app/components/app/loadable-peer";
import { useTrpcMutationStates } from "~app/hooks/use-trpc-mutation-state";
import { useTRPC } from "~app/utils/trpc";
import { AvatarGroup } from "~components/avatar";
import { Select } from "~components/select";
 
import { useActionsHooksContext, useReceiptContext } from "./context";
import { useCanEdit, useIsOwner } from "./hooks";
import type { Item } from "./state";
import { SORT_USERS } from "./utils";
 
type Props = {
	item: Item;
	className?: string;
};
 
export const ReceiptItemPayers: React.FC<Props> = ({ item, className }) => {
	const { t } = useTranslation("receipts");
	const { participants } = useReceiptContext();
	const { addItemPayer, removeItemPayer } = useActionsHooksContext();
	const isOwner = useIsOwner();
	const canEdit = useCanEdit();
	const trpc = useTRPC();
 
	const ownerPeerIds = participants
		.filter((participant) => participant.role === "owner")
		.map(({ peerId }) => peerId);
	const possibleParticipantIds = participants
		.map(({ peerId }) => peerId)
		.filter((participantId) => !ownerPeerIds.includes(participantId));
	const addedParticipantsIds = item.payers.map(({ peerId }) => peerId);
	const notAddedParticipantsIds = new Set(
		possibleParticipantIds.filter(
			(participantId) => !addedParticipantsIds.includes(participantId),
		),
	);
 
	const addConsumerMutationStates =
		useTrpcMutationStates<"receiptItemConsumers.add">(
			trpc.receiptItemConsumers.add.mutationKey(),
			({ itemId }) => itemId === item.id,
		);
	const removeConsumerMutationStates =
		useTrpcMutationStates<"receiptItemConsumers.remove">(
			trpc.receiptItemConsumers.remove.mutationKey(),
			({ itemId }) => itemId === item.id,
		);
 
	const defaultOwnerOnly = item.payers.length === 0;
	return (
		<Select
			className={className}
			label={t("item.payer.label")}
			placeholder={t("item.payer.placeholder")}
			selectedKeys={
				defaultOwnerOnly
					? ownerPeerIds
					: item.payers.map(({ peerId }) => peerId)
			}
			disabledKeys={
				addConsumerMutationStates.some(({ status }) => status === "pending") ||
				removeConsumerMutationStates.some(({ status }) => status === "pending")
					? possibleParticipantIds
					: undefined
			}
			onSelectionChange={(nextSelected) => {
				for (const id of addedParticipantsIds.filter(
					(candidateId) => !nextSelected.includes(candidateId),
				)) {
					removeItemPayer(item.id, id);
				}
				for (const id of nextSelected.filter((candidateId) =>
					notAddedParticipantsIds.has(candidateId),
				)) {
					addItemPayer(item.id, id, 1);
				}
			}}
			renderValue={(selectedParticipants) => {
				Eif (selectedParticipants.length === 1) {
					// oxlint-disable-next-line typescript/no-non-null-assertion
					const { peerId } = selectedParticipants[0]!;
					return (
						<LoadablePeer
							key={peerId}
							className={canEdit ? "cursor-pointer" : undefined}
							avatarProps={{ size: "sm", dimmed: defaultOwnerOnly }}
							id={peerId}
							foreign={!isOwner}
						/>
					);
				}
				return (
					<AvatarGroup
						className={canEdit ? "cursor-pointer" : undefined}
						size="sm"
					>
						{selectedParticipants
							.map((participant) => participant.peerId)
							.filter(isNonNullish)
							.map((peerId) => (
								<LoadablePeer key={peerId} id={peerId} foreign={!isOwner} />
							))}
					</AvatarGroup>
				);
			}}
			items={participants.toSorted(SORT_USERS)}
			getKey={({ peerId }) => peerId}
		>
			{({ peerId }) => <LoadablePeer id={peerId} foreign={!isOwner} />}
		</Select>
	);
};