All files / packages/app/hooks use-participants.ts

91.3% Statements 42/46
63.63% Branches 14/22
89.47% Functions 17/19
93.18% Lines 41/44

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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160                    112x 2331x 1259x         112x   7677x 2205x   5472x 305x     5167x     112x 2879x 2879x 1293x                 2879x     5369x 16092x   5369x         5369x                   112x 2331x 2331x 2331x 2331x     2331x       998x   998x   2331x 2331x   10137x 2498x         10137x         10137x 681x                                       9456x                                             2321x   10137x               2321x   1975x 10137x 8207x     2321x              
import React from "react";
 
import { useSuspenseQueries, useSuspenseQuery } from "@tanstack/react-query";
 
import { useDecimals } from "~app/hooks/use-decimals";
import type { Receipt, ReceiptParticipant } from "~app/trpc-types";
import { getParticipantSums } from "~app/utils/receipt-item";
import { useTRPC } from "~app/utils/trpc";
import type { PeerId } from "~db/ids";
 
const getDebtIds = (receipt: Pick<Receipt, "debts">) =>
	receipt.debts.direction === "outcoming"
		? receipt.debts.debts.map(({ id }) => id)
		: receipt.debts.id
			? [receipt.debts.id]
			: [];
 
const SORT_PARTICIPANTS = (a: ReceiptParticipant, b: ReceiptParticipant) => {
	// Sort first by owner
	if (a.role === "owner") {
		return -1;
	}
	if (b.role === "owner") {
		return 1;
	}
	// Sort everyone else by createdAt timestamp
	return Temporal.ZonedDateTime.compare(a.createdAt, b.createdAt);
};
 
export const useParticipants = (receipt: Omit<Receipt, "name">) => {
	const { fromUnitToSubunit, fromSubunitToUnit } = useDecimals();
	return React.useMemo(() => {
		const participantsSums = getParticipantSums(
			receipt.id,
			receipt.ownerPeerId,
			receipt.items,
			receipt.participants,
			receipt.payers,
			fromUnitToSubunit,
			fromSubunitToUnit,
		);
		return receipt.participants
			.toSorted(SORT_PARTICIPANTS)
			.map((participant) => {
				const matchedParticipant = participantsSums.find(
					({ peerId }) => peerId === participant.peerId,
				);
				Iif (!matchedParticipant) {
					throw new Error(
						"Expected to have matched participant on getting their sums",
					);
				}
				return {
					...participant,
					debt: matchedParticipant.debt,
					payment: matchedParticipant.payment,
					balance: matchedParticipant.balance,
				};
			});
	}, [fromSubunitToUnit, fromUnitToSubunit, receipt]);
};
 
export const useParticipantsWithDebts = (receipt: Omit<Receipt, "name">) => {
	const participants = useParticipants(receipt);
	const trpc = useTRPC();
	const debtIds = getDebtIds(receipt);
	const { data: intentions } = useSuspenseQuery(
		trpc.debtIntentions.getAll.queryOptions(),
	);
	const debts = useSuspenseQueries({
		queries: debtIds
			.filter(
				(debtId) =>
					!intentions.items.some((intention) => intention.id === debtId),
			)
			.map((debtId) => trpc.debts.get.queryOptions({ id: debtId })),
	});
	const isOwner = receipt.ownerPeerId === receipt.selfPeerId;
	const getDebt = React.useCallback(
		(participantPeerId: PeerId) => {
			const ownDebt = debts.find((debt) =>
				isOwner
					? debt.data.peerId === participantPeerId
					: debt.data.peerId === receipt.ownerPeerId &&
						participantPeerId === receipt.selfPeerId,
			)?.data;
			const incomingIntention = intentions.items.find(
				(intention) =>
					intention.peerId === participantPeerId &&
					intention.receiptId === receipt.id,
			);
			if (ownDebt) {
				return {
					id: ownDebt.id,
					peerId: ownDebt.peerId,
					receiptId: ownDebt.receiptId,
					our: {
						amount: ownDebt.amount,
						currencyCode: ownDebt.currencyCode,
						timestamp: ownDebt.timestamp,
						updatedAt: ownDebt.updatedAt,
					},
					their: ownDebt.their
						? {
								amount: ownDebt.their.amount,
								currencyCode: ownDebt.their.currencyCode,
								timestamp: ownDebt.their.timestamp,
								updatedAt: ownDebt.their.updatedAt,
							}
						: undefined,
				};
			}
			Iif (incomingIntention) {
				return {
					id: incomingIntention.id,
					peerId: incomingIntention.peerId,
					receiptId: incomingIntention.receiptId,
					their: {
						amount: incomingIntention.amount,
						currencyCode: incomingIntention.currencyCode,
						timestamp: incomingIntention.timestamp,
						updatedAt: incomingIntention.updatedAt,
					},
				};
			}
		},
		[
			debts,
			intentions,
			isOwner,
			receipt.id,
			receipt.ownerPeerId,
			receipt.selfPeerId,
		],
	);
	const participantsWithDebts = React.useMemo(
		() =>
			participants.map((participant) => ({
				...participant,
				currentDebt: getDebt(participant.peerId),
			})),
		[getDebt, participants],
	);
 
	// Debts that are non-zero and non-owner
	const syncableParticipants = React.useMemo(
		() =>
			participantsWithDebts
				.filter((participant) => participant.peerId !== receipt.ownerPeerId)
				.filter((participant) => participant.balance !== 0),
		[participantsWithDebts, receipt.ownerPeerId],
	);
	return {
		participantsWithDebts,
		syncableParticipants,
	};
};
 
export type Participant = ReturnType<typeof useParticipants>[number];